e6data-python-connector 2.3.9rc8__tar.gz → 2.3.9rc10__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 (77) hide show
  1. {e6data_python_connector-2.3.9rc8/e6data_python_connector.egg-info → e6data_python_connector-2.3.9rc10}/PKG-INFO +2 -2
  2. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/README.md +1 -1
  3. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/e6data_python_connector/cluster_manager.py +1 -1
  4. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/e6data_python_connector/dialect.py +3 -1
  5. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/e6data_python_connector/e6data_grpc.py +73 -36
  6. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/e6data_python_connector/strategy.py +1 -18
  7. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10/e6data_python_connector.egg-info}/PKG-INFO +2 -2
  8. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/setup.py +1 -1
  9. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/LICENSE +0 -0
  10. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/MANIFEST.in +0 -0
  11. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/e6data_python_connector/__init__.py +0 -0
  12. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/e6data_python_connector/cluster_server/__init__.py +0 -0
  13. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/e6data_python_connector/cluster_server/cluster_pb2.py +0 -0
  14. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/e6data_python_connector/cluster_server/cluster_pb2_grpc.py +0 -0
  15. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/e6data_python_connector/common.py +0 -0
  16. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/e6data_python_connector/constants.py +0 -0
  17. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/e6data_python_connector/datainputstream.py +0 -0
  18. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/e6data_python_connector/date_time_utils.py +0 -0
  19. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/e6data_python_connector/e6x_vector/__init__.py +0 -0
  20. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/e6data_python_connector/e6x_vector/constants.py +0 -0
  21. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/e6data_python_connector/e6x_vector/ttypes.py +0 -0
  22. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/e6data_python_connector/exceptions.py +0 -0
  23. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/e6data_python_connector/server/QueryEngineService.py +0 -0
  24. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/e6data_python_connector/server/__init__.py +0 -0
  25. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/e6data_python_connector/server/constants.py +0 -0
  26. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/e6data_python_connector/server/e6x_engine_pb2.py +0 -0
  27. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/e6data_python_connector/server/e6x_engine_pb2.pyi +0 -0
  28. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/e6data_python_connector/server/e6x_engine_pb2_grpc.py +0 -0
  29. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/e6data_python_connector/server/ttypes.py +0 -0
  30. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/e6data_python_connector/typeId.py +0 -0
  31. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/e6data_python_connector.egg-info/SOURCES.txt +0 -0
  32. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/e6data_python_connector.egg-info/dependency_links.txt +0 -0
  33. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/e6data_python_connector.egg-info/entry_points.txt +0 -0
  34. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/e6data_python_connector.egg-info/requires.txt +0 -0
  35. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/e6data_python_connector.egg-info/top_level.txt +0 -0
  36. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/pyproject.toml +0 -0
  37. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/setup.cfg +0 -0
  38. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/test/__init__.py +0 -0
  39. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/test/analyze_38_nines.py +0 -0
  40. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/test/analyze_all_cases.py +0 -0
  41. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/test/analyze_binary.py +0 -0
  42. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/test/analyze_correct_value.py +0 -0
  43. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/test/analyze_fields.py +0 -0
  44. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/test/check_decimal_errors.py +0 -0
  45. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/test/cleanup_test_files.py +0 -0
  46. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/test/debug_38_nines.py +0 -0
  47. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/test/debug_binary.py +0 -0
  48. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/test/final_test.py +0 -0
  49. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/test/move_tests.py +0 -0
  50. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/test/quick_test.py +0 -0
  51. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/test/test_38_nines.py +0 -0
  52. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/test/test_all_decimal128_cases.py +0 -0
  53. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/test/test_cluster_manager_efficiency.py +0 -0
  54. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/test/test_cluster_manager_none_strategy.py +0 -0
  55. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/test/test_cluster_manager_strategy.py +0 -0
  56. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/test/test_comprehensive.py +0 -0
  57. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/test/test_current_implementation.py +0 -0
  58. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/test/test_decimal128_binary_parsing.py +0 -0
  59. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/test/test_decimal128_parsing.py +0 -0
  60. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/test/test_fix.py +0 -0
  61. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/test/test_improved_parsing.py +0 -0
  62. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/test/test_known_case.py +0 -0
  63. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/test/test_manual_analysis.py +0 -0
  64. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/test/test_mock_server.py +0 -0
  65. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/test/test_multiprocessing_fix.py +0 -0
  66. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/test/test_new_implementation.py +0 -0
  67. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/test/test_specific_binary.py +0 -0
  68. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/test/test_strategy.py +0 -0
  69. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/test/test_strategy_logic.py +0 -0
  70. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/test/test_strategy_persistence_fix.py +0 -0
  71. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/test/test_strategy_sharing_fix.py +0 -0
  72. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/test/test_user_binary_value.py +0 -0
  73. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/test/tests.py +0 -0
  74. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/test/tests_grpc.py +0 -0
  75. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/test/validate_decimal128.py +0 -0
  76. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/test/validate_implementation.py +0 -0
  77. {e6data_python_connector-2.3.9rc8 → e6data_python_connector-2.3.9rc10}/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.9rc8
3
+ Version: 2.3.9rc10
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.
@@ -38,7 +38,7 @@ Dynamic: summary
38
38
 
39
39
  # e6data Python Connector
40
40
 
41
- ![version](https://img.shields.io/badge/version-2.3.9.rc8-blue.svg)
41
+ ![version](https://img.shields.io/badge/version-2.3.9rc10-blue.svg)
42
42
 
43
43
  ## Introduction
44
44
 
@@ -1,6 +1,6 @@
1
1
  # e6data Python Connector
2
2
 
3
- ![version](https://img.shields.io/badge/version-2.3.9.rc8-blue.svg)
3
+ ![version](https://img.shields.io/badge/version-2.3.9rc10-blue.svg)
4
4
 
5
5
  ## Introduction
6
6
 
@@ -365,7 +365,7 @@ class ClusterManager:
365
365
  # Retrieve the current cluster status with strategy header
366
366
  try:
367
367
  current_status = self._try_cluster_request("status")
368
- except _InactiveRpcError:
368
+ except _InactiveRpcError as e:
369
369
  return False
370
370
  if current_status.status == 'suspended':
371
371
  # Send the resume request with strategy header
@@ -229,6 +229,7 @@ class E6dataDialect(default.DefaultDialect):
229
229
  self.cluster_name = url.query.get("cluster-name")
230
230
  self.secure = url.query.get("secure") == "true"
231
231
  self.auto_resume = url.query.get("auto-resume", "true") == "true" # default to True
232
+ self.debug = url.query.get("debug", "false") == "true" # default to True
232
233
  if not self.catalog_name:
233
234
  raise Exception('Please specify catalog in query parameter.')
234
235
 
@@ -250,7 +251,8 @@ class E6dataDialect(default.DefaultDialect):
250
251
  "cluster_name": self.cluster_name,
251
252
  'secure': self.secure,
252
253
  'auto_resume': self.auto_resume,
253
- 'grpc_options': grpc_options
254
+ 'grpc_options': grpc_options,
255
+ 'debug': self.debug
254
256
  }
255
257
  return [], kwargs
256
258
 
@@ -128,26 +128,28 @@ _local_strategy_cache = {
128
128
  # Strategy cache timeout in seconds (5 minutes)
129
129
  STRATEGY_CACHE_TIMEOUT = 300
130
130
 
131
+ # Global set to track debug-enabled connections
132
+ _debug_connections = set()
133
+
134
+ def _strategy_debug_log(message):
135
+ """Log strategy debug messages if any connection has debug enabled."""
136
+ if _debug_connections:
137
+ print(f"[E6DATA_STRATEGY_DEBUG] {time.strftime('%Y-%m-%d %H:%M:%S')} - {message}")
138
+
139
+
140
+ def _get_normalised_strategy_with_validate(strategy):
141
+ if not strategy:
142
+ return False
143
+ # Normalize strategy to lowercase and validate
144
+ normalized_strategy = strategy.lower()
145
+ if normalized_strategy not in ['blue', 'green']:
146
+ return False
147
+ return normalized_strategy
148
+
131
149
 
132
150
  def _get_shared_strategy():
133
151
  """Get or create the shared strategy storage."""
134
- global _strategy_manager, _shared_strategy
135
-
136
- # Try to use multiprocessing.Manager for process-safe storage
137
- try:
138
- if _strategy_manager is None:
139
- _strategy_manager = multiprocessing.Manager()
140
- _shared_strategy = _strategy_manager.dict()
141
- _shared_strategy['active_strategy'] = None
142
- _shared_strategy['last_check_time'] = 0
143
- _shared_strategy['pending_strategy'] = None
144
- _shared_strategy['query_strategy_map'] = _strategy_manager.dict()
145
- _shared_strategy['last_transition_time'] = 0
146
- _shared_strategy['session_invalidated'] = False
147
- return _shared_strategy
148
- except:
149
- # Fall back to thread-local storage if Manager fails
150
- return _local_strategy_cache
152
+ return _local_strategy_cache
151
153
 
152
154
 
153
155
  def _get_active_strategy():
@@ -165,20 +167,21 @@ def _get_active_strategy():
165
167
 
166
168
  def _set_active_strategy(strategy):
167
169
  """Set the active deployment strategy in shared memory."""
168
- if not strategy:
169
- return
170
- # Normalize strategy to lowercase and validate
171
- normalized_strategy = strategy.lower()
172
- if normalized_strategy not in ['blue', 'green']:
170
+ normalized_strategy = _get_normalised_strategy_with_validate(strategy)
171
+ if not normalized_strategy:
173
172
  return
174
173
 
175
174
  with _strategy_lock:
176
175
  shared_strategy = _get_shared_strategy()
177
176
  current_time = time.time()
177
+ old_strategy = shared_strategy.get('active_strategy')
178
178
 
179
179
  # Only update transition time if strategy actually changed
180
- if shared_strategy['active_strategy'] != normalized_strategy:
180
+ if old_strategy != normalized_strategy:
181
181
  shared_strategy['last_transition_time'] = current_time
182
+ _strategy_debug_log(f"Setting active strategy: {old_strategy} -> {normalized_strategy}")
183
+ else:
184
+ _strategy_debug_log(f"Active strategy unchanged: {normalized_strategy}")
182
185
 
183
186
  shared_strategy['active_strategy'] = normalized_strategy
184
187
  shared_strategy['last_check_time'] = current_time
@@ -188,6 +191,9 @@ def _clear_strategy_cache():
188
191
  """Clear the cached strategy to force re-detection."""
189
192
  with _strategy_lock:
190
193
  shared_strategy = _get_shared_strategy()
194
+ old_strategy = shared_strategy.get('active_strategy')
195
+ if old_strategy:
196
+ _strategy_debug_log(f"Clearing strategy cache (was: {old_strategy})")
191
197
  shared_strategy['active_strategy'] = None
192
198
  shared_strategy['last_check_time'] = 0
193
199
  shared_strategy['pending_strategy'] = None
@@ -195,11 +201,8 @@ def _clear_strategy_cache():
195
201
 
196
202
  def _set_pending_strategy(strategy):
197
203
  """Set the pending strategy to be used for the next query."""
198
- if not strategy:
199
- return
200
- # Normalize strategy to lowercase and validate
201
- normalized_strategy = strategy.lower()
202
- if normalized_strategy not in ['blue', 'green']:
204
+ normalized_strategy = _get_normalised_strategy_with_validate(strategy)
205
+ if not normalized_strategy:
203
206
  return
204
207
 
205
208
  with _strategy_lock:
@@ -208,6 +211,8 @@ def _set_pending_strategy(strategy):
208
211
 
209
212
  if normalized_strategy != current_active:
210
213
  shared_strategy['pending_strategy'] = normalized_strategy
214
+ query_count = len(shared_strategy.get('query_strategy_map', {}))
215
+ _strategy_debug_log(f"Setting pending strategy: {normalized_strategy} (current: {current_active}, active queries: {query_count})")
211
216
 
212
217
 
213
218
  def _apply_pending_strategy():
@@ -215,14 +220,19 @@ def _apply_pending_strategy():
215
220
  with _strategy_lock:
216
221
  shared_strategy = _get_shared_strategy()
217
222
  if shared_strategy['pending_strategy']:
223
+ old_strategy = shared_strategy['active_strategy']
218
224
  new_strategy = shared_strategy['pending_strategy']
219
225
  current_time = time.time()
220
226
 
227
+ _strategy_debug_log(f"Applying pending strategy: {old_strategy} -> {new_strategy}")
228
+
221
229
  shared_strategy['active_strategy'] = new_strategy
222
230
  shared_strategy['pending_strategy'] = None
223
231
  shared_strategy['last_check_time'] = current_time
224
232
  shared_strategy['last_transition_time'] = current_time
225
233
  shared_strategy['session_invalidated'] = True # Invalidate all sessions
234
+
235
+ _strategy_debug_log(f"Strategy transition complete. All sessions invalidated.")
226
236
 
227
237
  return new_strategy
228
238
  return None
@@ -237,11 +247,8 @@ def _invalidate_all_sessions():
237
247
 
238
248
  def _register_query_strategy(query_id, strategy):
239
249
  """Register the strategy used for a specific query."""
240
- if not query_id or not strategy:
241
- return
242
- # Normalize strategy to lowercase and validate
243
- normalized_strategy = strategy.lower()
244
- if normalized_strategy not in ['blue', 'green']:
250
+ normalized_strategy = _get_normalised_strategy_with_validate(strategy)
251
+ if not normalized_strategy:
245
252
  return
246
253
 
247
254
  with _strategy_lock:
@@ -249,6 +256,7 @@ def _register_query_strategy(query_id, strategy):
249
256
  query_map = shared_strategy.get('query_strategy_map', {})
250
257
  query_map[query_id] = normalized_strategy
251
258
  shared_strategy['query_strategy_map'] = query_map
259
+ _strategy_debug_log(f"Query {query_id} registered with strategy: {normalized_strategy}")
252
260
 
253
261
 
254
262
  def _get_query_strategy(query_id):
@@ -270,8 +278,11 @@ def _cleanup_query_strategy(query_id):
270
278
  shared_strategy = _get_shared_strategy()
271
279
  query_map = shared_strategy.get('query_strategy_map', {})
272
280
  if query_id in query_map:
281
+ strategy = query_map[query_id]
273
282
  del query_map[query_id]
274
283
  shared_strategy['query_strategy_map'] = query_map
284
+ remaining_queries = len(query_map)
285
+ _strategy_debug_log(f"Query {query_id} completed (was using {strategy}). Remaining active queries: {remaining_queries}")
275
286
 
276
287
 
277
288
  def _get_strategy_debug_info():
@@ -318,6 +329,7 @@ class Connection(object):
318
329
  auto_resume: bool = True,
319
330
  scheme: str = 'e6data',
320
331
  grpc_options: dict = None,
332
+ debug: bool = False,
321
333
  ):
322
334
  """
323
335
  Parameters
@@ -349,6 +361,8 @@ class Connection(object):
349
361
  - max_send_message_length: Similar to max_receive_message_length, this parameter sets the maximum allowed size (in bytes) for outgoing messages from the gRPC client
350
362
  - grpc_prepare_timeout: Timeout for prepare statement API call (default to 10 minutes).
351
363
  - keepalive_time_ms: This parameter defines the time, in milliseconds, Default to 30 seconds
364
+ debug: bool, Optional
365
+ Flag to enable debug logging for blue-green deployment strategy changes
352
366
  """
353
367
  if not username or not password:
354
368
  raise ValueError("username or password cannot be empty.")
@@ -378,6 +392,13 @@ class Connection(object):
378
392
  The default maximum time on client side to wait for the cluster to resume is 5 minutes.
379
393
  """
380
394
  self.grpc_auto_resume_timeout_seconds = self._grpc_options.pop('grpc_auto_resume_timeout_seconds')
395
+
396
+ # Store debug flag and register with debug connections
397
+ self._debug = debug
398
+ if self._debug:
399
+ _debug_connections.add(id(self))
400
+ _strategy_debug_log(f"Debug mode enabled for connection {id(self)}")
401
+
381
402
  self._create_client()
382
403
 
383
404
  @property
@@ -504,6 +525,7 @@ class Connection(object):
504
525
 
505
526
  if active_strategy and not pending_strategy:
506
527
  # Use cached strategy only if there's no pending strategy
528
+ _strategy_debug_log(f"Authenticating with cached strategy: {active_strategy}")
507
529
  try:
508
530
  authenticate_response = self._client.authenticate(
509
531
  authenticate_request,
@@ -515,8 +537,9 @@ class Connection(object):
515
537
  # Check for new strategy in authenticate response
516
538
  if hasattr(authenticate_response, 'new_strategy') and authenticate_response.new_strategy:
517
539
  new_strategy = authenticate_response.new_strategy.lower()
518
- if new_strategy != active_strategy:
519
- _set_pending_strategy(new_strategy)
540
+ normalized_strategy = _get_normalised_strategy_with_validate(new_strategy)
541
+ if normalized_strategy and normalized_strategy != active_strategy:
542
+ _set_pending_strategy(normalized_strategy)
520
543
  _apply_pending_strategy()
521
544
  self._session_id = None
522
545
  self.close()
@@ -525,6 +548,7 @@ class Connection(object):
525
548
  except _InactiveRpcError as e:
526
549
  if e.code() == grpc.StatusCode.UNKNOWN and 'status: 456' in e.details():
527
550
  # Strategy changed, clear cache and retry
551
+ _strategy_debug_log(f"Got 456 error with strategy {active_strategy}, clearing cache and retrying")
528
552
  _clear_strategy_cache()
529
553
  active_strategy = None
530
554
  else:
@@ -543,8 +567,10 @@ class Connection(object):
543
567
  else:
544
568
  # Always try blue first, then green if it fails with 456
545
569
  strategies = ['blue', 'green']
570
+ _strategy_debug_log(f"No cached strategy, will try strategies in order: {strategies}")
546
571
  last_error = None
547
572
  for strategy in strategies:
573
+ _strategy_debug_log(f"Attempting authentication with strategy: {strategy}")
548
574
  try:
549
575
  authenticate_response = self._client.authenticate(
550
576
  authenticate_request,
@@ -553,12 +579,14 @@ class Connection(object):
553
579
  self._session_id = authenticate_response.sessionId
554
580
  if self._session_id:
555
581
  # Success! Cache this strategy
582
+ _strategy_debug_log(f"Authentication successful with strategy: {strategy}")
556
583
  _set_active_strategy(strategy)
557
584
 
558
585
  # Check for new strategy in authenticate response
559
586
  if hasattr(authenticate_response, 'new_strategy') and authenticate_response.new_strategy:
560
587
  new_strategy = authenticate_response.new_strategy.lower()
561
- if new_strategy != strategy:
588
+ normalized_strategy = _get_normalised_strategy_with_validate(new_strategy)
589
+ if normalized_strategy and normalized_strategy != active_strategy:
562
590
  _set_pending_strategy(new_strategy)
563
591
  _apply_pending_strategy()
564
592
  self.close()
@@ -569,6 +597,7 @@ class Connection(object):
569
597
  except _InactiveRpcError as e:
570
598
  if e.code() == grpc.StatusCode.UNKNOWN and 'status: 456' in e.details():
571
599
  # Wrong strategy, try the next one
600
+ _strategy_debug_log(f"Strategy {strategy} failed with 456 error, trying next")
572
601
  last_error = e
573
602
  continue
574
603
  else:
@@ -593,6 +622,7 @@ class Connection(object):
593
622
  cluster_uuid=self.cluster_name,
594
623
  timeout=self.grpc_auto_resume_timeout_seconds
595
624
  ).resume()
625
+ print('starus', status)
596
626
  if status:
597
627
  return self.get_session_id
598
628
  else:
@@ -640,6 +670,11 @@ class Connection(object):
640
670
  self._channel.close()
641
671
  self._channel = None
642
672
  self._session_id = None
673
+
674
+ # Remove from debug connections if debug was enabled
675
+ if self._debug:
676
+ _debug_connections.discard(id(self))
677
+ _strategy_debug_log(f"Debug mode disabled for connection {id(self)}")
643
678
 
644
679
  def check_connection(self):
645
680
  """
@@ -1086,6 +1121,7 @@ class Cursor(DBAPICursor):
1086
1121
  query_map = shared_strategy.get('query_strategy_map', {})
1087
1122
 
1088
1123
  if pending_strategy and len(query_map) == 0:
1124
+ _strategy_debug_log(f"Last query cleared, triggering pending strategy transition")
1089
1125
  _apply_pending_strategy()
1090
1126
 
1091
1127
  return clear_response
@@ -1109,6 +1145,7 @@ class Cursor(DBAPICursor):
1109
1145
  query_map = shared_strategy.get('query_strategy_map', {})
1110
1146
 
1111
1147
  if pending_strategy and len(query_map) == 0:
1148
+ _strategy_debug_log(f"Last query cleared, triggering pending strategy transition")
1112
1149
  _apply_pending_strategy()
1113
1150
 
1114
1151
  def status(self, query_id):
@@ -40,24 +40,7 @@ def _initialize_shared_state():
40
40
  with _initialization_lock:
41
41
  if _shared_strategy is not None:
42
42
  return _shared_strategy
43
-
44
- try:
45
- # Try to create multiprocessing Manager
46
- _manager = multiprocessing.Manager()
47
- _shared_strategy = _manager.dict({
48
- 'active_strategy': None,
49
- 'last_check_time': 0,
50
- 'pending_strategy': None,
51
- 'query_strategy_map': _manager.dict(),
52
- 'last_transition_time': 0,
53
- 'session_invalidated': False
54
- })
55
- _logger.debug("Successfully initialized multiprocessing Manager for strategy sharing")
56
- return _shared_strategy
57
- except Exception as e:
58
- # Fall back to thread-local storage if Manager fails
59
- _logger.warning(f"Failed to initialize multiprocessing Manager: {e}. Using thread-local storage.")
60
- return _local_strategy_cache
43
+ return _local_strategy_cache
61
44
 
62
45
 
63
46
  def _get_shared_strategy():
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: e6data-python-connector
3
- Version: 2.3.9rc8
3
+ Version: 2.3.9rc10
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.
@@ -38,7 +38,7 @@ Dynamic: summary
38
38
 
39
39
  # e6data Python Connector
40
40
 
41
- ![version](https://img.shields.io/badge/version-2.3.9.rc8-blue.svg)
41
+ ![version](https://img.shields.io/badge/version-2.3.9rc10-blue.svg)
42
42
 
43
43
  ## Introduction
44
44
 
@@ -12,7 +12,7 @@
12
12
 
13
13
  import setuptools
14
14
 
15
- VERSION = (2, 3, 9, 'rc8')
15
+ VERSION = (2, 3, 9, 'rc10',)
16
16
 
17
17
 
18
18
  def get_long_desc():