e6data-python-connector 2.3.11rc1__tar.gz → 2.3.12rc1__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.
Files changed (87) hide show
  1. {e6data_python_connector-2.3.11rc1/e6data_python_connector.egg-info → e6data_python_connector-2.3.12rc1}/PKG-INFO +57 -2
  2. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/README.md +56 -1
  3. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/e6data_python_connector/cluster_manager.py +93 -9
  4. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/e6data_python_connector/e6data_grpc.py +75 -32
  5. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1/e6data_python_connector.egg-info}/PKG-INFO +57 -2
  6. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/setup.py +1 -1
  7. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/LICENSE +0 -0
  8. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/MANIFEST.in +0 -0
  9. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/e6data_python_connector/__init__.py +0 -0
  10. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/e6data_python_connector/cluster_server/__init__.py +0 -0
  11. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/e6data_python_connector/cluster_server/cluster_pb2.py +0 -0
  12. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/e6data_python_connector/cluster_server/cluster_pb2_grpc.py +0 -0
  13. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/e6data_python_connector/common.py +0 -0
  14. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/e6data_python_connector/connection_pool.py +0 -0
  15. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/e6data_python_connector/constants.py +0 -0
  16. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/e6data_python_connector/datainputstream.py +0 -0
  17. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/e6data_python_connector/date_time_utils.py +0 -0
  18. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/e6data_python_connector/dialect.py +0 -0
  19. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/e6data_python_connector/e6x_vector/__init__.py +0 -0
  20. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/e6data_python_connector/e6x_vector/constants.py +0 -0
  21. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/e6data_python_connector/e6x_vector/ttypes.py +0 -0
  22. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/e6data_python_connector/exceptions.py +0 -0
  23. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/e6data_python_connector/server/QueryEngineService.py +0 -0
  24. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/e6data_python_connector/server/__init__.py +0 -0
  25. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/e6data_python_connector/server/constants.py +0 -0
  26. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/e6data_python_connector/server/e6x_engine_pb2.py +0 -0
  27. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/e6data_python_connector/server/e6x_engine_pb2.pyi +0 -0
  28. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/e6data_python_connector/server/e6x_engine_pb2_grpc.py +0 -0
  29. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/e6data_python_connector/server/ttypes.py +0 -0
  30. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/e6data_python_connector/strategy.py +0 -0
  31. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/e6data_python_connector/typeId.py +0 -0
  32. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/e6data_python_connector.egg-info/SOURCES.txt +0 -0
  33. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/e6data_python_connector.egg-info/dependency_links.txt +0 -0
  34. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/e6data_python_connector.egg-info/entry_points.txt +0 -0
  35. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/e6data_python_connector.egg-info/requires.txt +0 -0
  36. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/e6data_python_connector.egg-info/top_level.txt +0 -0
  37. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/gen-py/__init__.py +0 -0
  38. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/gen-py/e6x_vector/__init__.py +0 -0
  39. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/gen-py/e6x_vector/constants.py +0 -0
  40. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/gen-py/e6x_vector/ttypes.py +0 -0
  41. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/pyproject.toml +0 -0
  42. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/setup.cfg +0 -0
  43. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/__init__.py +0 -0
  44. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/analyze_38_nines.py +0 -0
  45. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/analyze_all_cases.py +0 -0
  46. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/analyze_binary.py +0 -0
  47. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/analyze_correct_value.py +0 -0
  48. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/analyze_fields.py +0 -0
  49. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/check_decimal_errors.py +0 -0
  50. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/cleanup_test_files.py +0 -0
  51. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/debug_38_nines.py +0 -0
  52. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/debug_binary.py +0 -0
  53. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/final_test.py +0 -0
  54. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/move_tests.py +0 -0
  55. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/quick_test.py +0 -0
  56. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/test_38_nines.py +0 -0
  57. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/test_all_decimal128_cases.py +0 -0
  58. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/test_cluster_manager_efficiency.py +0 -0
  59. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/test_cluster_manager_none_strategy.py +0 -0
  60. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/test_cluster_manager_strategy.py +0 -0
  61. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/test_comprehensive.py +0 -0
  62. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/test_connection_pool.py +0 -0
  63. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/test_connection_pool_e2e.py +0 -0
  64. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/test_current_implementation.py +0 -0
  65. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/test_decimal128_binary_parsing.py +0 -0
  66. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/test_decimal128_parsing.py +0 -0
  67. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/test_fix.py +0 -0
  68. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/test_improved_parsing.py +0 -0
  69. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/test_known_case.py +0 -0
  70. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/test_manual_analysis.py +0 -0
  71. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/test_mock_server.py +0 -0
  72. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/test_multiprocessing_fix.py +0 -0
  73. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/test_new_implementation.py +0 -0
  74. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/test_pool_concurrency_simple.py +0 -0
  75. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/test_pool_threading_multiprocessing.py +0 -0
  76. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/test_simple_connection_pool.py +0 -0
  77. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/test_specific_binary.py +0 -0
  78. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/test_strategy.py +0 -0
  79. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/test_strategy_logic.py +0 -0
  80. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/test_strategy_persistence_fix.py +0 -0
  81. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/test_strategy_sharing_fix.py +0 -0
  82. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/test_user_binary_value.py +0 -0
  83. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/tests.py +0 -0
  84. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/tests_grpc.py +0 -0
  85. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/validate_decimal128.py +0 -0
  86. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/validate_implementation.py +0 -0
  87. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.12rc1}/test/verify_decimal_fix.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: e6data-python-connector
3
- Version: 2.3.11rc1
3
+ Version: 2.3.12rc1
4
4
  Summary: Client for the e6data distributed SQL Engine.
5
5
  Home-page: https://github.com/e6x-labs/e6data-python-connector
6
6
  Author: e6data, Inc.
@@ -39,7 +39,7 @@ Dynamic: summary
39
39
 
40
40
  # e6data Python Connector
41
41
 
42
- ![version](https://img.shields.io/badge/version-2.3.11rc1-blue.svg)
42
+ ![version](https://img.shields.io/badge/version-2.3.12rc1-blue.svg)
43
43
 
44
44
  ## Introduction
45
45
 
@@ -124,6 +124,7 @@ The `Connection` class supports the following parameters:
124
124
  | `secure` | bool | No | False | Enable SSL/TLS for secure connections |
125
125
  | `auto_resume` | bool | No | True | Automatically resume cluster if suspended |
126
126
  | `grpc_options` | dict | No | None | Additional gRPC configuration options |
127
+ | `debug` | bool | No | False | Enable debug logging for troubleshooting |
127
128
 
128
129
  #### Secure Connection Example
129
130
 
@@ -637,4 +638,58 @@ conn.close()
637
638
  - Graceful connection recovery and retry logic
638
639
  - Blue-green deployment support with automatic failover
639
640
 
641
+ ## Debugging and Troubleshooting
642
+
643
+ ### Enable Debug Mode
644
+
645
+ Enable comprehensive debugging to troubleshoot connection and query issues:
646
+
647
+ ```python
648
+ from e6data_python_connector import Connection
649
+
650
+ conn = Connection(
651
+ host=host,
652
+ port=port,
653
+ username=username,
654
+ password=password,
655
+ database=database,
656
+ debug=True # Enable debug logging
657
+ )
658
+ ```
659
+
660
+ When `debug=True`, the following features are enabled:
661
+ - Python logging at DEBUG level for all operations
662
+ - Blue-green strategy transition logging
663
+ - Connection lifecycle logging
664
+ - Query execution detailed logging
665
+
666
+ ### gRPC Network Tracing
667
+
668
+ For low-level gRPC network debugging (HTTP/2 frames, TCP events), set environment variables **before** running your Python script:
669
+
670
+ ```bash
671
+ # Enable gRPC network tracing
672
+ export GRPC_VERBOSITY=DEBUG
673
+ export GRPC_TRACE=client_channel,http2
674
+
675
+ # For comprehensive tracing
676
+ export GRPC_TRACE=api,call_error,channel,client_channel,connectivity_state,http,http2_stream,tcp,transport_security
677
+
678
+ # Run your script
679
+ python your_script.py
680
+ ```
681
+
682
+ **Note**: These environment variables must be set before Python starts, as the gRPC C++ core reads them at module import time.
683
+
684
+ ### Common Issues and Solutions
685
+
686
+ | Issue | Solution |
687
+ |-------|----------|
688
+ | Connection timeout | Check network connectivity, firewall rules, and ensure port 80/443 is open |
689
+ | Authentication failure | Verify username (email) and access token are correct |
690
+ | 503 Service Unavailable | Cluster may be suspended; enable `auto_resume=True` |
691
+ | 456 Strategy Error | Automatic blue-green failover will handle this |
692
+ | Memory issues with large results | Use `fetchall_buffer()` instead of `fetchall()` |
693
+ | gRPC message size errors | Configure `grpc_options` with appropriate message size limits |
694
+
640
695
  See [TECH_DOC.md](TECH_DOC.md) for detailed technical documentation.
@@ -1,6 +1,6 @@
1
1
  # e6data Python Connector
2
2
 
3
- ![version](https://img.shields.io/badge/version-2.3.11rc1-blue.svg)
3
+ ![version](https://img.shields.io/badge/version-2.3.12rc1-blue.svg)
4
4
 
5
5
  ## Introduction
6
6
 
@@ -85,6 +85,7 @@ The `Connection` class supports the following parameters:
85
85
  | `secure` | bool | No | False | Enable SSL/TLS for secure connections |
86
86
  | `auto_resume` | bool | No | True | Automatically resume cluster if suspended |
87
87
  | `grpc_options` | dict | No | None | Additional gRPC configuration options |
88
+ | `debug` | bool | No | False | Enable debug logging for troubleshooting |
88
89
 
89
90
  #### Secure Connection Example
90
91
 
@@ -598,4 +599,58 @@ conn.close()
598
599
  - Graceful connection recovery and retry logic
599
600
  - Blue-green deployment support with automatic failover
600
601
 
602
+ ## Debugging and Troubleshooting
603
+
604
+ ### Enable Debug Mode
605
+
606
+ Enable comprehensive debugging to troubleshoot connection and query issues:
607
+
608
+ ```python
609
+ from e6data_python_connector import Connection
610
+
611
+ conn = Connection(
612
+ host=host,
613
+ port=port,
614
+ username=username,
615
+ password=password,
616
+ database=database,
617
+ debug=True # Enable debug logging
618
+ )
619
+ ```
620
+
621
+ When `debug=True`, the following features are enabled:
622
+ - Python logging at DEBUG level for all operations
623
+ - Blue-green strategy transition logging
624
+ - Connection lifecycle logging
625
+ - Query execution detailed logging
626
+
627
+ ### gRPC Network Tracing
628
+
629
+ For low-level gRPC network debugging (HTTP/2 frames, TCP events), set environment variables **before** running your Python script:
630
+
631
+ ```bash
632
+ # Enable gRPC network tracing
633
+ export GRPC_VERBOSITY=DEBUG
634
+ export GRPC_TRACE=client_channel,http2
635
+
636
+ # For comprehensive tracing
637
+ export GRPC_TRACE=api,call_error,channel,client_channel,connectivity_state,http,http2_stream,tcp,transport_security
638
+
639
+ # Run your script
640
+ python your_script.py
641
+ ```
642
+
643
+ **Note**: These environment variables must be set before Python starts, as the gRPC C++ core reads them at module import time.
644
+
645
+ ### Common Issues and Solutions
646
+
647
+ | Issue | Solution |
648
+ |-------|----------|
649
+ | Connection timeout | Check network connectivity, firewall rules, and ensure port 80/443 is open |
650
+ | Authentication failure | Verify username (email) and access token are correct |
651
+ | 503 Service Unavailable | Cluster may be suspended; enable `auto_resume=True` |
652
+ | 456 Strategy Error | Automatic blue-green failover will handle this |
653
+ | Memory issues with large results | Use `fetchall_buffer()` instead of `fetchall()` |
654
+ | gRPC message size errors | Configure `grpc_options` with appropriate message size limits |
655
+
601
656
  See [TECH_DOC.md](TECH_DOC.md) for detailed technical documentation.
@@ -1,3 +1,4 @@
1
+ import logging
1
2
  import threading
2
3
  import time
3
4
  import e6data_python_connector.cluster_server.cluster_pb2 as cluster_pb2
@@ -6,6 +7,8 @@ import grpc
6
7
  from grpc._channel import _InactiveRpcError
7
8
  import multiprocessing
8
9
 
10
+ logger = logging.getLogger(__name__)
11
+
9
12
  from e6data_python_connector.strategy import _get_active_strategy, _set_active_strategy, _set_pending_strategy, \
10
13
  _get_grpc_header as _get_strategy_header
11
14
 
@@ -137,7 +140,7 @@ class ClusterManager:
137
140
  """
138
141
 
139
142
  def __init__(self, host: str, port: int, user: str, password: str, secure_channel: bool = False, timeout=60 * 5,
140
- cluster_uuid=None, grpc_options=None):
143
+ cluster_uuid=None, grpc_options=None, debug=False):
141
144
  """
142
145
  Initializes a new instance of the ClusterManager class.
143
146
 
@@ -152,6 +155,7 @@ class ClusterManager:
152
155
  defaults to 5 minutes.
153
156
  cluster_uuid (str, optional): The unique identifier for the target cluster;
154
157
  defaults to None.
158
+ debug (bool, optional): Enable debug logging; defaults to False.
155
159
  """
156
160
 
157
161
  self._host = host
@@ -164,6 +168,7 @@ class ClusterManager:
164
168
  self._grpc_options = grpc_options
165
169
  if grpc_options is None:
166
170
  self._grpc_options = dict()
171
+ self._debug = debug
167
172
 
168
173
  @property
169
174
  def _get_connection(self):
@@ -191,20 +196,22 @@ class ClusterManager:
191
196
  def _try_cluster_request(self, request_type, payload=None):
192
197
  """
193
198
  Execute a cluster request with strategy fallback for 456 errors.
194
-
199
+
195
200
  For efficiency:
196
201
  - If we have an active strategy, use it first
197
202
  - Only try authentication sequence (blue -> green) if no active strategy
198
203
  - On 456 error, switch to alternative strategy and update active strategy
199
-
204
+
200
205
  Args:
201
206
  request_type: Type of request ('status' or 'resume')
202
207
  payload: Request payload (optional, will be created if not provided)
203
-
208
+
204
209
  Returns:
205
210
  The response from the successful request
206
211
  """
207
212
  current_strategy = _get_active_strategy()
213
+ if self._debug:
214
+ logger.info(f"Attempting {request_type} request with current strategy: {current_strategy}")
208
215
 
209
216
  # Create payload if not provided
210
217
  if payload is None:
@@ -222,6 +229,8 @@ class ClusterManager:
222
229
  # If we have an active strategy, use it first
223
230
  if current_strategy is not None:
224
231
  try:
232
+ if self._debug:
233
+ logger.info(f"Executing {request_type} with strategy '{current_strategy}'")
225
234
  if request_type == "status":
226
235
  response = self._get_connection.status(
227
236
  payload,
@@ -239,16 +248,24 @@ class ClusterManager:
239
248
  if hasattr(response, 'new_strategy') and response.new_strategy:
240
249
  new_strategy = response.new_strategy.lower()
241
250
  if new_strategy != current_strategy:
251
+ if self._debug:
252
+ logger.info(f"Server indicated strategy transition from '{current_strategy}' to '{new_strategy}'")
242
253
  _set_pending_strategy(new_strategy)
243
254
 
255
+ if self._debug:
256
+ logger.info(f"{request_type} request successful with strategy '{current_strategy}'")
244
257
  return response
245
258
 
246
259
  except _InactiveRpcError as e:
247
260
  if e.code() == grpc.StatusCode.UNKNOWN and 'status: 456' in e.details():
248
261
  # 456 error - switch to alternative strategy
249
262
  alternative_strategy = 'green' if current_strategy == 'blue' else 'blue'
263
+ if self._debug:
264
+ logger.info(f"Received 456 error with strategy '{current_strategy}', switching to '{alternative_strategy}'")
250
265
 
251
266
  try:
267
+ if self._debug:
268
+ logger.info(f"Retrying {request_type} with alternative strategy '{alternative_strategy}'")
252
269
  if request_type == "status":
253
270
  response = self._get_connection.status(
254
271
  payload,
@@ -261,27 +278,39 @@ class ClusterManager:
261
278
  )
262
279
 
263
280
  # Update active strategy since the alternative worked
281
+ if self._debug:
282
+ logger.info(f"Successfully switched to strategy '{alternative_strategy}'")
264
283
  _set_active_strategy(alternative_strategy)
265
284
 
266
285
  # Check for new strategy in response
267
286
  if hasattr(response, 'new_strategy') and response.new_strategy:
268
287
  new_strategy = response.new_strategy.lower()
269
288
  if new_strategy != alternative_strategy:
289
+ if self._debug:
290
+ logger.info(f"Server indicated future strategy transition to '{new_strategy}'")
270
291
  _set_pending_strategy(new_strategy)
271
292
 
272
293
  return response
273
294
 
274
295
  except _InactiveRpcError as e2:
296
+ if self._debug:
297
+ logger.error(f"Failed with alternative strategy '{alternative_strategy}': {e2}")
275
298
  raise e # Raise the original error
276
299
  else:
277
300
  # Non-456 error - don't retry
301
+ if self._debug:
302
+ logger.info(f"Non-456 error received, not retrying: {e}")
278
303
  raise e
279
304
 
280
305
  # No active strategy - start with authentication logic (blue first, then green)
281
306
  strategies_to_try = ['blue', 'green']
307
+ if self._debug:
308
+ logger.info("No active strategy found, trying authentication sequence: blue -> green")
282
309
 
283
310
  for i, strategy in enumerate(strategies_to_try):
284
311
  try:
312
+ if self._debug:
313
+ logger.info(f"Trying {request_type} with strategy '{strategy}' (attempt {i+1}/{len(strategies_to_try)})")
285
314
 
286
315
  if request_type == "status":
287
316
  response = self._get_connection.status(
@@ -297,12 +326,16 @@ class ClusterManager:
297
326
  raise ValueError(f"Unknown request type: {request_type}")
298
327
 
299
328
  # Set the working strategy as active
329
+ if self._debug:
330
+ logger.info(f"Successfully authenticated with strategy '{strategy}'")
300
331
  _set_active_strategy(strategy)
301
332
 
302
333
  # Check for new strategy in response
303
334
  if hasattr(response, 'new_strategy') and response.new_strategy:
304
335
  new_strategy = response.new_strategy.lower()
305
336
  if new_strategy != strategy:
337
+ if self._debug:
338
+ logger.info(f"Server indicated strategy transition to '{new_strategy}'")
306
339
  _set_pending_strategy(new_strategy)
307
340
 
308
341
  return response
@@ -311,14 +344,22 @@ class ClusterManager:
311
344
  if e.code() == grpc.StatusCode.UNKNOWN and 'status: 456' in e.details():
312
345
  # 456 error - try next strategy
313
346
  if i < len(strategies_to_try) - 1:
347
+ if self._debug:
348
+ logger.info(f"Strategy '{strategy}' returned 456 error, trying next strategy")
314
349
  continue
315
350
  else:
351
+ if self._debug:
352
+ logger.error(f"All strategies failed with 456 errors")
316
353
  raise e
317
354
  else:
318
355
  # Non-456 error - don't retry
356
+ if self._debug:
357
+ logger.error(f"Strategy '{strategy}' failed with non-456 error: {e}")
319
358
  raise e
320
359
 
321
360
  # If we get here, all strategies failed
361
+ if self._debug:
362
+ logger.error("All authentication strategies exhausted")
322
363
  raise e
323
364
 
324
365
  def _check_cluster_status(self):
@@ -326,8 +367,12 @@ class ClusterManager:
326
367
  try:
327
368
  # Use the unified strategy-aware request method
328
369
  response = self._try_cluster_request("status")
370
+ if self._debug:
371
+ logger.info(f"Cluster status check returned: {response.status}")
329
372
  yield response.status
330
- except _InactiveRpcError:
373
+ except _InactiveRpcError as e:
374
+ if self._debug:
375
+ logger.warning(f"Status check failed with error: {e}")
331
376
  yield None
332
377
 
333
378
  def resume(self) -> bool:
@@ -357,38 +402,77 @@ class ClusterManager:
357
402
  - The operation is subject to the `_timeout` threshold;
358
403
  if the timeout expires, the method returns False.
359
404
  """
405
+ if self._debug:
406
+ logger.info(f"Starting auto-resume for cluster {self.cluster_uuid} at {self._host}:{self._port}")
360
407
 
361
408
  with status_lock as lock:
362
409
  if lock.is_active:
410
+ if self._debug:
411
+ logger.info("Lock is already active, cluster appears to be running")
363
412
  return True
364
413
 
365
414
  # Retrieve the current cluster status with strategy header
415
+ if self._debug:
416
+ logger.info("Checking current cluster status")
366
417
  try:
367
418
  current_status = self._try_cluster_request("status")
368
- except _InactiveRpcError:
419
+ if self._debug:
420
+ logger.info(f"Current cluster status: {current_status.status}")
421
+ except _InactiveRpcError as e:
422
+ if self._debug:
423
+ logger.error(f"Failed to get cluster status: {e}")
369
424
  return False
425
+
370
426
  if current_status.status == 'suspended':
371
427
  # Send the resume request with strategy header
428
+ if self._debug:
429
+ logger.info("Cluster is suspended, sending resume request")
372
430
  try:
373
431
  response = self._try_cluster_request("resume")
374
- except _InactiveRpcError:
432
+ if self._debug:
433
+ logger.info("Resume request sent successfully")
434
+ except _InactiveRpcError as e:
435
+ if self._debug:
436
+ logger.error(f"Failed to send resume request: {e}")
375
437
  return False
376
438
  elif current_status.status == 'active':
439
+ if self._debug:
440
+ logger.info("Cluster is already active, no action needed")
377
441
  return True
378
442
  elif current_status.status != 'resuming':
379
443
  """
380
- If cluster cannot be resumed due to its current state,
444
+ If cluster cannot be resumed due to its current state,
381
445
  or already in a process of resuming, terminate the operation.
382
446
  """
447
+ if self._debug:
448
+ logger.warning(f"Cluster is in state '{current_status.status}' which cannot be resumed")
383
449
  return False
384
450
 
451
+ if self._debug:
452
+ logger.info("Monitoring cluster status until it becomes active...")
453
+ check_count = 0
385
454
  for status in self._check_cluster_status():
455
+ check_count += 1
386
456
  if status == 'active':
457
+ if self._debug:
458
+ logger.info(f"Cluster became active after {check_count} status checks")
387
459
  return True
388
- elif status == 'failed' or time.time() > self._timeout:
460
+ elif status == 'failed':
461
+ if self._debug:
462
+ logger.error(f"Cluster failed to resume after {check_count} status checks")
463
+ return False
464
+ elif time.time() > self._timeout:
465
+ if self._debug:
466
+ logger.error(f"Resume operation timed out after {check_count} status checks")
389
467
  return False
468
+
469
+ if self._debug:
470
+ logger.info(f"Status check #{check_count}: {status}, waiting 5 seconds before next check")
390
471
  # Wait for 5 seconds before the next status check
391
472
  time.sleep(5)
473
+
474
+ if self._debug:
475
+ logger.warning("Resume operation completed without reaching active state")
392
476
  return False
393
477
 
394
478
  def suspend(self):
@@ -9,6 +9,7 @@ from __future__ import unicode_literals
9
9
  import datetime
10
10
  import logging
11
11
  import os
12
+
12
13
  import re
13
14
  import sys
14
15
  import time
@@ -114,6 +115,9 @@ class HiveParamEscaper(ParamEscaper):
114
115
 
115
116
  _escaper = HiveParamEscaper()
116
117
 
118
+ # Logger for the module
119
+ logger = logging.getLogger(__name__)
120
+
117
121
  # Thread-safe and process-safe storage for active deployment strategy
118
122
  _strategy_lock = threading.Lock()
119
123
  _strategy_manager = None
@@ -136,7 +140,7 @@ _debug_connections = set()
136
140
  def _strategy_debug_log(message):
137
141
  """Log strategy debug messages if any connection has debug enabled."""
138
142
  if _debug_connections:
139
- print(f"[E6DATA_STRATEGY_DEBUG] {time.strftime('%Y-%m-%d %H:%M:%S')} - {message}")
143
+ logger.info(f"[E6DATA_STRATEGY_DEBUG] {time.strftime('%Y-%m-%d %H:%M:%S')} - {message}")
140
144
 
141
145
 
142
146
  def _get_shared_strategy():
@@ -384,6 +388,7 @@ class Connection(object):
384
388
  self._auto_resume = auto_resume
385
389
 
386
390
  self._grpc_options = grpc_options
391
+
387
392
  if self._grpc_options is None:
388
393
  self._grpc_options = dict()
389
394
  self.grpc_prepare_timeout = self._grpc_options.get('grpc_prepare_timeout') or 10 * 60 # 10 minutes
@@ -393,7 +398,15 @@ class Connection(object):
393
398
  The default maximum time on client side to wait for the cluster to resume is 5 minutes.
394
399
  """
395
400
  self.grpc_auto_resume_timeout_seconds = self._grpc_options.pop('grpc_auto_resume_timeout_seconds')
396
-
401
+
402
+ self.timeout = 500
403
+ if 'timeout' in self._grpc_options:
404
+ """
405
+ The default maximum time on client side to wait for the cluster to resume is 5 minutes.
406
+ """
407
+ self.timeout = self._grpc_options.pop('timeout')
408
+
409
+
397
410
  # Store debug flag and register with debug connections
398
411
  self._debug = debug
399
412
  if self._debug:
@@ -409,21 +422,39 @@ class Connection(object):
409
422
  force=True # Force reconfiguration even if logging is already configured
410
423
  )
411
424
 
412
- # Enable gRPC debug logging
413
- os.environ['GRPC_VERBOSITY'] = 'DEBUG'
414
- os.environ['GRPC_TRACE'] = 'all'
425
+ # Note: gRPC C++ core tracing (GRPC_VERBOSITY and GRPC_TRACE) must be set
426
+ # BEFORE the gRPC module is imported to take effect. Setting them at runtime
427
+ # will not enable HTTP/2 frame logs or low-level tracing.
428
+ #
429
+ # To enable full gRPC network tracing, set these environment variables
430
+ # before starting your Python script:
431
+ # export GRPC_VERBOSITY=DEBUG
432
+ # export GRPC_TRACE=client_channel,http2
433
+ #
434
+ # The following runtime settings only affect Python-level logging:
435
+
436
+ # Enable gRPC Python logging (this works at runtime)
437
+ os.environ['GRPC_PYTHON_LOG_LEVEL'] = 'DEBUG'
438
+ os.environ['GRPC_PYTHON_LOG_STDERR'] = '1'
415
439
 
416
440
  # Ensure gRPC logger is at DEBUG level
417
441
  grpc_logger = logging.getLogger('grpc')
418
442
  grpc_logger.setLevel(logging.DEBUG)
419
443
 
444
+ # Enable gRPC transport logger
445
+ grpc_transport_logger = logging.getLogger('grpc._channel')
446
+ grpc_transport_logger.setLevel(logging.DEBUG)
447
+
448
+ # Enable gRPC server logger
449
+ grpc_server_logger = logging.getLogger('grpc._server')
450
+ grpc_server_logger.setLevel(logging.DEBUG)
451
+
420
452
  # Set e6data connector logger to DEBUG
421
453
  e6data_logger = logging.getLogger('e6data_python_connector')
422
454
  e6data_logger.setLevel(logging.DEBUG)
423
455
 
424
456
  _strategy_debug_log(f"Debug mode enabled for connection {id(self)}")
425
- _strategy_debug_log(f"Root logger level: {logging.getLogger().level}")
426
- _strategy_debug_log(f"gRPC debugging enabled with GRPC_VERBOSITY=DEBUG and GRPC_TRACE=all")
457
+ _strategy_debug_log(f"GRPC_TRACE={os.environ.get('GRPC_TRACE')}")
427
458
 
428
459
  self._create_client()
429
460
 
@@ -578,7 +609,12 @@ class Connection(object):
578
609
  _clear_strategy_cache()
579
610
  active_strategy = None
580
611
  else:
581
- raise e
612
+ if self._perform_auto_resume(e):
613
+ # Cluster resumed, retry with cached strategy
614
+ _strategy_debug_log(f"Cluster resumed, retrying with cached strategy {active_strategy}")
615
+ active_strategy = None # Force retry
616
+ else:
617
+ raise e
582
618
  elif pending_strategy:
583
619
  # If there's a pending strategy, force re-authentication with new strategy
584
620
  active_strategy = None
@@ -596,7 +632,7 @@ class Connection(object):
596
632
  _strategy_debug_log(f"No cached strategy, will try strategies in order: {strategies}")
597
633
  last_error = None
598
634
  for strategy in strategies:
599
- _strategy_debug_log(f"Attempting authentication with strategy: {strategy}")
635
+ _strategy_debug_log(f"Attempting authentication with strategy: {strategy}.")
600
636
  try:
601
637
  authenticate_response = self._client.authenticate(
602
638
  authenticate_request,
@@ -626,8 +662,13 @@ class Connection(object):
626
662
  last_error = e
627
663
  continue
628
664
  else:
629
- # Different error, handle it normally
630
- raise e
665
+ if self._perform_auto_resume(e):
666
+ # Cluster resumed successfully, retry authentication with current strategy
667
+ _strategy_debug_log(f"Cluster resumed, retrying authentication with {strategy}")
668
+ continue
669
+ else:
670
+ # Auto-resume failed, raise the error
671
+ raise e
631
672
 
632
673
  if not self._session_id and last_error:
633
674
  # Neither strategy worked
@@ -636,30 +677,31 @@ class Connection(object):
636
677
  if not self._session_id:
637
678
  raise ValueError("Invalid credentials.")
638
679
  except _InactiveRpcError as e:
639
- if self._auto_resume:
640
- if e.code() == grpc.StatusCode.UNAVAILABLE and 'status: 503' in e.details():
641
- status = ClusterManager(
642
- host=self._host,
643
- port=self._port,
644
- user=self.__username,
645
- password=self.__password,
646
- secure_channel=self._secure_channel,
647
- cluster_uuid=self.cluster_name,
648
- timeout=self.grpc_auto_resume_timeout_seconds
649
- ).resume()
650
- if status:
651
- return self.get_session_id
652
- else:
653
- raise e
654
- else:
655
- raise e
656
- else:
657
- raise e
680
+ self._perform_auto_resume(e)
658
681
  except Exception as e:
659
682
  self._channel.close()
660
683
  raise e
661
684
  return self._session_id
662
685
 
686
+ def _perform_auto_resume(self, e: _InactiveRpcError):
687
+ if self._auto_resume:
688
+ if e.code() == grpc.StatusCode.UNAVAILABLE and 'status: 503' in e.details():
689
+ status = ClusterManager(
690
+ host=self._host,
691
+ port=self._port,
692
+ user=self.__username,
693
+ password=self.__password,
694
+ secure_channel=self._secure_channel,
695
+ cluster_uuid=self.cluster_name,
696
+ timeout=self.grpc_auto_resume_timeout_seconds,
697
+ debug=self._debug
698
+ ).resume()
699
+ return status # Return boolean status directly
700
+ else:
701
+ return False # Non-503 error, cannot auto-resume
702
+ else:
703
+ return False # Auto-resume disabled
704
+
663
705
  def __enter__(self):
664
706
  """
665
707
  Enters the runtime context related to this object.
@@ -1410,13 +1452,14 @@ class Cursor(DBAPICursor):
1410
1452
  get_next_result_batch_request = e6x_engine_pb2.GetNextResultBatchRequest(
1411
1453
  engineIP=self._engine_ip,
1412
1454
  sessionId=self.connection.get_session_id,
1413
- queryId=self._query_id
1455
+ queryId=self._query_id,
1414
1456
  )
1415
1457
  # Get fresh client after session access (may have been invalidated)
1416
1458
  client = self.connection.client
1417
1459
  get_next_result_batch_response = client.getNextResultBatch(
1418
1460
  get_next_result_batch_request,
1419
- metadata=self.metadata
1461
+ metadata=self.metadata,
1462
+ timeout=self.connection.timeout
1420
1463
  )
1421
1464
 
1422
1465
  # Check for new strategy in batch response
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: e6data-python-connector
3
- Version: 2.3.11rc1
3
+ Version: 2.3.12rc1
4
4
  Summary: Client for the e6data distributed SQL Engine.
5
5
  Home-page: https://github.com/e6x-labs/e6data-python-connector
6
6
  Author: e6data, Inc.
@@ -39,7 +39,7 @@ Dynamic: summary
39
39
 
40
40
  # e6data Python Connector
41
41
 
42
- ![version](https://img.shields.io/badge/version-2.3.11rc1-blue.svg)
42
+ ![version](https://img.shields.io/badge/version-2.3.12rc1-blue.svg)
43
43
 
44
44
  ## Introduction
45
45
 
@@ -124,6 +124,7 @@ The `Connection` class supports the following parameters:
124
124
  | `secure` | bool | No | False | Enable SSL/TLS for secure connections |
125
125
  | `auto_resume` | bool | No | True | Automatically resume cluster if suspended |
126
126
  | `grpc_options` | dict | No | None | Additional gRPC configuration options |
127
+ | `debug` | bool | No | False | Enable debug logging for troubleshooting |
127
128
 
128
129
  #### Secure Connection Example
129
130
 
@@ -637,4 +638,58 @@ conn.close()
637
638
  - Graceful connection recovery and retry logic
638
639
  - Blue-green deployment support with automatic failover
639
640
 
641
+ ## Debugging and Troubleshooting
642
+
643
+ ### Enable Debug Mode
644
+
645
+ Enable comprehensive debugging to troubleshoot connection and query issues:
646
+
647
+ ```python
648
+ from e6data_python_connector import Connection
649
+
650
+ conn = Connection(
651
+ host=host,
652
+ port=port,
653
+ username=username,
654
+ password=password,
655
+ database=database,
656
+ debug=True # Enable debug logging
657
+ )
658
+ ```
659
+
660
+ When `debug=True`, the following features are enabled:
661
+ - Python logging at DEBUG level for all operations
662
+ - Blue-green strategy transition logging
663
+ - Connection lifecycle logging
664
+ - Query execution detailed logging
665
+
666
+ ### gRPC Network Tracing
667
+
668
+ For low-level gRPC network debugging (HTTP/2 frames, TCP events), set environment variables **before** running your Python script:
669
+
670
+ ```bash
671
+ # Enable gRPC network tracing
672
+ export GRPC_VERBOSITY=DEBUG
673
+ export GRPC_TRACE=client_channel,http2
674
+
675
+ # For comprehensive tracing
676
+ export GRPC_TRACE=api,call_error,channel,client_channel,connectivity_state,http,http2_stream,tcp,transport_security
677
+
678
+ # Run your script
679
+ python your_script.py
680
+ ```
681
+
682
+ **Note**: These environment variables must be set before Python starts, as the gRPC C++ core reads them at module import time.
683
+
684
+ ### Common Issues and Solutions
685
+
686
+ | Issue | Solution |
687
+ |-------|----------|
688
+ | Connection timeout | Check network connectivity, firewall rules, and ensure port 80/443 is open |
689
+ | Authentication failure | Verify username (email) and access token are correct |
690
+ | 503 Service Unavailable | Cluster may be suspended; enable `auto_resume=True` |
691
+ | 456 Strategy Error | Automatic blue-green failover will handle this |
692
+ | Memory issues with large results | Use `fetchall_buffer()` instead of `fetchall()` |
693
+ | gRPC message size errors | Configure `grpc_options` with appropriate message size limits |
694
+
640
695
  See [TECH_DOC.md](TECH_DOC.md) for detailed technical documentation.
@@ -12,7 +12,7 @@
12
12
 
13
13
  import setuptools
14
14
 
15
- VERSION = (2, 3, 11, 'rc1')
15
+ VERSION = (2, 3, 12, 'rc1',)
16
16
 
17
17
 
18
18
  def get_long_desc():