e6data-python-connector 2.3.11rc1__tar.gz → 2.3.11rc2__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.11rc2}/PKG-INFO +2 -2
  2. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/README.md +1 -1
  3. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/cluster_manager.py +93 -9
  4. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/datainputstream.py +19 -31
  5. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/e6data_grpc.py +39 -52
  6. {e6data_python_connector-2.3.11rc1/gen-py → e6data_python_connector-2.3.11rc2/e6data_python_connector}/e6x_vector/ttypes.py +2 -26
  7. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2/e6data_python_connector.egg-info}/PKG-INFO +2 -2
  8. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/setup.py +1 -1
  9. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/LICENSE +0 -0
  10. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/MANIFEST.in +0 -0
  11. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/__init__.py +0 -0
  12. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/cluster_server/__init__.py +0 -0
  13. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/cluster_server/cluster_pb2.py +0 -0
  14. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/cluster_server/cluster_pb2_grpc.py +0 -0
  15. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/common.py +0 -0
  16. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/connection_pool.py +0 -0
  17. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/constants.py +0 -0
  18. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/date_time_utils.py +0 -0
  19. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/dialect.py +0 -0
  20. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/e6x_vector/__init__.py +0 -0
  21. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/e6x_vector/constants.py +0 -0
  22. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/exceptions.py +0 -0
  23. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/server/QueryEngineService.py +0 -0
  24. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/server/__init__.py +0 -0
  25. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/server/constants.py +0 -0
  26. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/server/e6x_engine_pb2.py +0 -0
  27. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/server/e6x_engine_pb2.pyi +0 -0
  28. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/server/e6x_engine_pb2_grpc.py +0 -0
  29. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/server/ttypes.py +0 -0
  30. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/strategy.py +0 -0
  31. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/typeId.py +0 -0
  32. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector.egg-info/SOURCES.txt +0 -0
  33. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector.egg-info/dependency_links.txt +0 -0
  34. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector.egg-info/entry_points.txt +0 -0
  35. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector.egg-info/requires.txt +0 -0
  36. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector.egg-info/top_level.txt +0 -0
  37. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/gen-py/__init__.py +0 -0
  38. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/gen-py/e6x_vector/__init__.py +0 -0
  39. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/gen-py/e6x_vector/constants.py +0 -0
  40. {e6data_python_connector-2.3.11rc1/e6data_python_connector → e6data_python_connector-2.3.11rc2/gen-py}/e6x_vector/ttypes.py +0 -0
  41. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/pyproject.toml +0 -0
  42. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/setup.cfg +0 -0
  43. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/__init__.py +0 -0
  44. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/analyze_38_nines.py +0 -0
  45. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/analyze_all_cases.py +0 -0
  46. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/analyze_binary.py +0 -0
  47. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/analyze_correct_value.py +0 -0
  48. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/analyze_fields.py +0 -0
  49. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/check_decimal_errors.py +0 -0
  50. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/cleanup_test_files.py +0 -0
  51. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/debug_38_nines.py +0 -0
  52. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/debug_binary.py +0 -0
  53. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/final_test.py +0 -0
  54. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/move_tests.py +0 -0
  55. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/quick_test.py +0 -0
  56. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_38_nines.py +0 -0
  57. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_all_decimal128_cases.py +0 -0
  58. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_cluster_manager_efficiency.py +0 -0
  59. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_cluster_manager_none_strategy.py +0 -0
  60. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_cluster_manager_strategy.py +0 -0
  61. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_comprehensive.py +0 -0
  62. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_connection_pool.py +0 -0
  63. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_connection_pool_e2e.py +0 -0
  64. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_current_implementation.py +0 -0
  65. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_decimal128_binary_parsing.py +0 -0
  66. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_decimal128_parsing.py +0 -0
  67. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_fix.py +0 -0
  68. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_improved_parsing.py +0 -0
  69. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_known_case.py +0 -0
  70. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_manual_analysis.py +0 -0
  71. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_mock_server.py +0 -0
  72. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_multiprocessing_fix.py +0 -0
  73. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_new_implementation.py +0 -0
  74. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_pool_concurrency_simple.py +0 -0
  75. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_pool_threading_multiprocessing.py +0 -0
  76. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_simple_connection_pool.py +0 -0
  77. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_specific_binary.py +0 -0
  78. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_strategy.py +0 -0
  79. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_strategy_logic.py +0 -0
  80. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_strategy_persistence_fix.py +0 -0
  81. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_strategy_sharing_fix.py +0 -0
  82. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_user_binary_value.py +0 -0
  83. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/tests.py +0 -0
  84. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/tests_grpc.py +0 -0
  85. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/validate_decimal128.py +0 -0
  86. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/validate_implementation.py +0 -0
  87. {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/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.11rc2
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.11rc2-blue.svg)
43
43
 
44
44
  ## Introduction
45
45
 
@@ -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.11rc2-blue.svg)
4
4
 
5
5
  ## Introduction
6
6
 
@@ -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):
@@ -26,18 +26,16 @@ except ImportError:
26
26
  _logger = logging.getLogger(__name__)
27
27
 
28
28
 
29
- def _binary_to_decimal128(binary_data, scale=None):
29
+ def _binary_to_decimal128(binary_data):
30
30
  """
31
31
  Convert binary data to Decimal128.
32
-
32
+
33
33
  The binary data represents a 128-bit decimal number in IEEE 754-2008 Decimal128 format.
34
34
  Based on the Java implementation from e6data's JDBC driver.
35
-
35
+
36
36
  Args:
37
37
  binary_data (bytes): Binary representation of Decimal128
38
- scale (int, optional): Scale parameter for decimal precision. If None,
39
- attempts to decode from IEEE format or defaults to 0.
40
-
38
+
41
39
  Returns:
42
40
  Decimal: Python Decimal object
43
41
  """
@@ -61,7 +59,7 @@ def _binary_to_decimal128(binary_data, scale=None):
61
59
 
62
60
  # Handle IEEE 754-2008 Decimal128 binary format
63
61
  if len(binary_data) == 16: # Decimal128 should be exactly 16 bytes
64
- return _decode_decimal128_binary_java_style(binary_data, scale)
62
+ return _decode_decimal128_binary_java_style(binary_data)
65
63
  else:
66
64
  _logger.warning(f"Invalid Decimal128 binary length: {len(binary_data)} bytes, expected 16")
67
65
  return Decimal('0')
@@ -75,17 +73,16 @@ def _binary_to_decimal128(binary_data, scale=None):
75
73
  return Decimal('0')
76
74
 
77
75
 
78
- def _decode_decimal128_binary_java_style(binary_data, scale=None):
76
+ def _decode_decimal128_binary_java_style(binary_data):
79
77
  """
80
78
  Decode IEEE 754-2008 Decimal128 binary format following Java implementation.
81
-
79
+
82
80
  Based on the Java implementation from e6data's JDBC driver getFieldDataFromChunk method.
83
81
  This method follows the same logic as the Java BigDecimal creation from ByteBuffer.
84
-
82
+
85
83
  Args:
86
84
  binary_data (bytes): 16-byte binary representation
87
- scale (int, optional): Scale parameter for decimal precision
88
-
85
+
89
86
  Returns:
90
87
  Decimal: Python Decimal object
91
88
  """
@@ -106,21 +103,17 @@ def _decode_decimal128_binary_java_style(binary_data, scale=None):
106
103
  if big_int_value == 0:
107
104
  return Decimal('0')
108
105
 
109
- # The Java code creates BigDecimal from BigInteger with the provided scale
110
- # If scale is provided, use it to create the properly scaled decimal
111
- if scale is not None:
112
- # Create decimal with the specified scale (like Java's BigDecimal constructor)
113
- # This treats big_int_value as the unscaled value
114
- decimal_value = Decimal(big_int_value) / (Decimal(10) ** scale)
115
- else:
116
- # Fallback: try to create decimal directly from the integer value
117
- decimal_value = Decimal(big_int_value)
106
+ # The Java code creates BigDecimal from BigInteger with scale 0
107
+ # This means we treat the integer value as the unscaled value
108
+ # However, for Decimal128, we need to handle the scaling properly
109
+
110
+ # Try to create decimal directly from the integer value
111
+ decimal_value = Decimal(big_int_value)
118
112
 
119
113
  # Check if this produces a reasonable decimal value
120
114
  # Decimal128 should represent normal decimal numbers
121
- # Only check range if scale was not provided (backward compatibility)
122
- if scale is None and (abs(decimal_value) < Decimal('1E-6143') or abs(decimal_value) > Decimal(
123
- '9.999999999999999999999999999999999E+6144')):
115
+ if abs(decimal_value) < Decimal('1E-6143') or abs(decimal_value) > Decimal(
116
+ '9.999999999999999999999999999999999E+6144'):
124
117
  # Value is outside normal Decimal128 range, try alternative interpretation
125
118
  return _decode_decimal128_alternative(binary_data)
126
119
 
@@ -631,12 +624,10 @@ def get_column_from_chunk(vector: Vector) -> list:
631
624
  if vector.isConstantVector:
632
625
  # For constant vectors, get the binary data and convert it once
633
626
  binary_data = vector.data.numericDecimal128ConstantData.data
634
- # Get scale with backward compatibility for older engines
635
- scale = getattr(vector.data.numericDecimal128ConstantData, 'scale', None)
636
627
 
637
628
  # Convert binary data to BigDecimal equivalent
638
629
  if binary_data:
639
- decimal_value = _binary_to_decimal128(binary_data, scale)
630
+ decimal_value = _binary_to_decimal128(binary_data)
640
631
  else:
641
632
  decimal_value = Decimal('0')
642
633
 
@@ -648,16 +639,13 @@ def get_column_from_chunk(vector: Vector) -> list:
648
639
  value_array.append(decimal_value)
649
640
  else:
650
641
  # For non-constant vectors, process each row individually
651
- # Get scale from decimal128Data (with backward compatibility)
652
- scale = getattr(vector.data.decimal128Data, 'scale', None)
653
-
654
642
  for row in range(vector.size):
655
643
  if get_null(vector, row):
656
644
  value_array.append(None)
657
645
  continue
658
646
  # Get binary data for this row
659
647
  binary_data = vector.data.decimal128Data.data[row]
660
- decimal_value = _binary_to_decimal128(binary_data, scale)
648
+ decimal_value = _binary_to_decimal128(binary_data)
661
649
  value_array.append(decimal_value)
662
650
  else:
663
651
  value_array.append(None)
@@ -8,7 +8,6 @@ from __future__ import unicode_literals
8
8
 
9
9
  import datetime
10
10
  import logging
11
- import os
12
11
  import re
13
12
  import sys
14
13
  import time
@@ -114,6 +113,9 @@ class HiveParamEscaper(ParamEscaper):
114
113
 
115
114
  _escaper = HiveParamEscaper()
116
115
 
116
+ # Logger for the module
117
+ logger = logging.getLogger(__name__)
118
+
117
119
  # Thread-safe and process-safe storage for active deployment strategy
118
120
  _strategy_lock = threading.Lock()
119
121
  _strategy_manager = None
@@ -136,7 +138,7 @@ _debug_connections = set()
136
138
  def _strategy_debug_log(message):
137
139
  """Log strategy debug messages if any connection has debug enabled."""
138
140
  if _debug_connections:
139
- print(f"[E6DATA_STRATEGY_DEBUG] {time.strftime('%Y-%m-%d %H:%M:%S')} - {message}")
141
+ logger.info(f"[E6DATA_STRATEGY_DEBUG] {time.strftime('%Y-%m-%d %H:%M:%S')} - {message}")
140
142
 
141
143
 
142
144
  def _get_shared_strategy():
@@ -398,33 +400,8 @@ class Connection(object):
398
400
  self._debug = debug
399
401
  if self._debug:
400
402
  _debug_connections.add(id(self))
401
-
402
- # Enable comprehensive debugging if debug flag is set
403
- if self._debug:
404
- # Configure root logger for DEBUG level
405
- logging.basicConfig(
406
- level=logging.DEBUG,
407
- format='[%(name)s] %(asctime)s - %(levelname)s - %(message)s',
408
- datefmt='%Y-%m-%d %H:%M:%S',
409
- force=True # Force reconfiguration even if logging is already configured
410
- )
411
-
412
- # Enable gRPC debug logging
413
- os.environ['GRPC_VERBOSITY'] = 'DEBUG'
414
- os.environ['GRPC_TRACE'] = 'all'
415
-
416
- # Ensure gRPC logger is at DEBUG level
417
- grpc_logger = logging.getLogger('grpc')
418
- grpc_logger.setLevel(logging.DEBUG)
419
-
420
- # Set e6data connector logger to DEBUG
421
- e6data_logger = logging.getLogger('e6data_python_connector')
422
- e6data_logger.setLevel(logging.DEBUG)
423
-
424
403
  _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")
427
-
404
+
428
405
  self._create_client()
429
406
 
430
407
  @property
@@ -476,7 +453,6 @@ class Connection(object):
476
453
  Raises:
477
454
  grpc.RpcError: If there is an error in creating the gRPC channel or client stub.
478
455
  """
479
-
480
456
  if self._secure_channel:
481
457
  self._channel = grpc.secure_channel(
482
458
  target='{}:{}'.format(self._host, self._port),
@@ -578,7 +554,12 @@ class Connection(object):
578
554
  _clear_strategy_cache()
579
555
  active_strategy = None
580
556
  else:
581
- raise e
557
+ if self._perform_auto_resume(e):
558
+ # Cluster resumed, retry with cached strategy
559
+ _strategy_debug_log(f"Cluster resumed, retrying with cached strategy {active_strategy}")
560
+ active_strategy = None # Force retry
561
+ else:
562
+ raise e
582
563
  elif pending_strategy:
583
564
  # If there's a pending strategy, force re-authentication with new strategy
584
565
  active_strategy = None
@@ -596,7 +577,7 @@ class Connection(object):
596
577
  _strategy_debug_log(f"No cached strategy, will try strategies in order: {strategies}")
597
578
  last_error = None
598
579
  for strategy in strategies:
599
- _strategy_debug_log(f"Attempting authentication with strategy: {strategy}")
580
+ _strategy_debug_log(f"1Attempting authentication with strategy: {strategy}")
600
581
  try:
601
582
  authenticate_response = self._client.authenticate(
602
583
  authenticate_request,
@@ -626,8 +607,13 @@ class Connection(object):
626
607
  last_error = e
627
608
  continue
628
609
  else:
629
- # Different error, handle it normally
630
- raise e
610
+ if self._perform_auto_resume(e):
611
+ # Cluster resumed successfully, retry authentication with current strategy
612
+ _strategy_debug_log(f"Cluster resumed, retrying authentication with {strategy}")
613
+ continue
614
+ else:
615
+ # Auto-resume failed, raise the error
616
+ raise e
631
617
 
632
618
  if not self._session_id and last_error:
633
619
  # Neither strategy worked
@@ -636,30 +622,31 @@ class Connection(object):
636
622
  if not self._session_id:
637
623
  raise ValueError("Invalid credentials.")
638
624
  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
625
+ self._perform_auto_resume(e)
658
626
  except Exception as e:
659
627
  self._channel.close()
660
628
  raise e
661
629
  return self._session_id
662
630
 
631
+ def _perform_auto_resume(self, e: _InactiveRpcError):
632
+ if self._auto_resume:
633
+ if e.code() == grpc.StatusCode.UNAVAILABLE and 'status: 503' in e.details():
634
+ status = ClusterManager(
635
+ host=self._host,
636
+ port=self._port,
637
+ user=self.__username,
638
+ password=self.__password,
639
+ secure_channel=self._secure_channel,
640
+ cluster_uuid=self.cluster_name,
641
+ timeout=self.grpc_auto_resume_timeout_seconds,
642
+ debug=self._debug
643
+ ).resume()
644
+ return status # Return boolean status directly
645
+ else:
646
+ return False # Non-503 error, cannot auto-resume
647
+ else:
648
+ return False # Auto-resume disabled
649
+
663
650
  def __enter__(self):
664
651
  """
665
652
  Enters the runtime context related to this object.
@@ -881,15 +881,13 @@ class Decimal128Data(object):
881
881
  """
882
882
  Attributes:
883
883
  - data
884
- - scale
885
884
 
886
885
  """
887
886
  thrift_spec = None
888
887
 
889
888
 
890
- def __init__(self, data = None, scale = None,):
889
+ def __init__(self, data = None,):
891
890
  self.data = data
892
- self.scale = scale
893
891
 
894
892
  def read(self, iprot):
895
893
  if iprot._fast_decode is not None and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None:
@@ -910,11 +908,6 @@ class Decimal128Data(object):
910
908
  iprot.readListEnd()
911
909
  else:
912
910
  iprot.skip(ftype)
913
- elif fid == 2:
914
- if ftype == TType.I32:
915
- self.scale = iprot.readI32()
916
- else:
917
- iprot.skip(ftype)
918
911
  else:
919
912
  iprot.skip(ftype)
920
913
  iprot.readFieldEnd()
@@ -933,10 +926,6 @@ class Decimal128Data(object):
933
926
  oprot.writeBinary(iter55)
934
927
  oprot.writeListEnd()
935
928
  oprot.writeFieldEnd()
936
- if self.scale is not None:
937
- oprot.writeFieldBegin('scale', TType.I32, 2)
938
- oprot.writeI32(self.scale)
939
- oprot.writeFieldEnd()
940
929
  oprot.writeFieldStop()
941
930
  oprot.writeStructEnd()
942
931
 
@@ -1321,15 +1310,13 @@ class NumericDecimal128ConstantData(object):
1321
1310
  """
1322
1311
  Attributes:
1323
1312
  - data
1324
- - scale
1325
1313
 
1326
1314
  """
1327
1315
  thrift_spec = None
1328
1316
 
1329
1317
 
1330
- def __init__(self, data = None, scale = None,):
1318
+ def __init__(self, data = None,):
1331
1319
  self.data = data
1332
- self.scale = scale
1333
1320
 
1334
1321
  def read(self, iprot):
1335
1322
  if iprot._fast_decode is not None and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None:
@@ -1345,11 +1332,6 @@ class NumericDecimal128ConstantData(object):
1345
1332
  self.data = iprot.readBinary()
1346
1333
  else:
1347
1334
  iprot.skip(ftype)
1348
- elif fid == 2:
1349
- if ftype == TType.I32:
1350
- self.scale = iprot.readI32()
1351
- else:
1352
- iprot.skip(ftype)
1353
1335
  else:
1354
1336
  iprot.skip(ftype)
1355
1337
  iprot.readFieldEnd()
@@ -1365,10 +1347,6 @@ class NumericDecimal128ConstantData(object):
1365
1347
  oprot.writeFieldBegin('data', TType.STRING, 1)
1366
1348
  oprot.writeBinary(self.data)
1367
1349
  oprot.writeFieldEnd()
1368
- if self.scale is not None:
1369
- oprot.writeFieldBegin('scale', TType.I32, 2)
1370
- oprot.writeI32(self.scale)
1371
- oprot.writeFieldEnd()
1372
1350
  oprot.writeFieldStop()
1373
1351
  oprot.writeStructEnd()
1374
1352
 
@@ -1805,7 +1783,6 @@ all_structs.append(Decimal128Data)
1805
1783
  Decimal128Data.thrift_spec = (
1806
1784
  None, # 0
1807
1785
  (1, TType.LIST, 'data', (TType.STRING, 'BINARY', False), None, ), # 1
1808
- (2, TType.I32, 'scale', None, None, ), # 2
1809
1786
  )
1810
1787
  all_structs.append(VarcharData)
1811
1788
  VarcharData.thrift_spec = (
@@ -1841,7 +1818,6 @@ all_structs.append(NumericDecimal128ConstantData)
1841
1818
  NumericDecimal128ConstantData.thrift_spec = (
1842
1819
  None, # 0
1843
1820
  (1, TType.STRING, 'data', 'BINARY', None, ), # 1
1844
- (2, TType.I32, 'scale', None, None, ), # 2
1845
1821
  )
1846
1822
  all_structs.append(TemporalIntervalConstantData)
1847
1823
  TemporalIntervalConstantData.thrift_spec = (
@@ -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.11rc2
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.11rc2-blue.svg)
43
43
 
44
44
  ## Introduction
45
45
 
@@ -12,7 +12,7 @@
12
12
 
13
13
  import setuptools
14
14
 
15
- VERSION = (2, 3, 11, 'rc1')
15
+ VERSION = (2, 3, 11, 'rc2')
16
16
 
17
17
 
18
18
  def get_long_desc():