e6data-python-connector 2.3.7rc2__py3-none-any.whl → 2.3.9__py3-none-any.whl

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.
@@ -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,18 @@ _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
+
131
139
 
132
140
  def _get_shared_strategy():
133
141
  """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
142
+ return _local_strategy_cache
151
143
 
152
144
 
153
145
  def _get_active_strategy():
@@ -175,10 +167,14 @@ def _set_active_strategy(strategy):
175
167
  with _strategy_lock:
176
168
  shared_strategy = _get_shared_strategy()
177
169
  current_time = time.time()
170
+ old_strategy = shared_strategy.get('active_strategy')
178
171
 
179
172
  # Only update transition time if strategy actually changed
180
- if shared_strategy['active_strategy'] != normalized_strategy:
173
+ if old_strategy != normalized_strategy:
181
174
  shared_strategy['last_transition_time'] = current_time
175
+ _strategy_debug_log(f"Setting active strategy: {old_strategy} -> {normalized_strategy}")
176
+ else:
177
+ _strategy_debug_log(f"Active strategy unchanged: {normalized_strategy}")
182
178
 
183
179
  shared_strategy['active_strategy'] = normalized_strategy
184
180
  shared_strategy['last_check_time'] = current_time
@@ -188,6 +184,9 @@ def _clear_strategy_cache():
188
184
  """Clear the cached strategy to force re-detection."""
189
185
  with _strategy_lock:
190
186
  shared_strategy = _get_shared_strategy()
187
+ old_strategy = shared_strategy.get('active_strategy')
188
+ if old_strategy:
189
+ _strategy_debug_log(f"Clearing strategy cache (was: {old_strategy})")
191
190
  shared_strategy['active_strategy'] = None
192
191
  shared_strategy['last_check_time'] = 0
193
192
  shared_strategy['pending_strategy'] = None
@@ -208,6 +207,8 @@ def _set_pending_strategy(strategy):
208
207
 
209
208
  if normalized_strategy != current_active:
210
209
  shared_strategy['pending_strategy'] = normalized_strategy
210
+ query_count = len(shared_strategy.get('query_strategy_map', {}))
211
+ _strategy_debug_log(f"Setting pending strategy: {normalized_strategy} (current: {current_active}, active queries: {query_count})")
211
212
 
212
213
 
213
214
  def _apply_pending_strategy():
@@ -219,11 +220,15 @@ def _apply_pending_strategy():
219
220
  new_strategy = shared_strategy['pending_strategy']
220
221
  current_time = time.time()
221
222
 
223
+ _strategy_debug_log(f"Applying pending strategy: {old_strategy} -> {new_strategy}")
224
+
222
225
  shared_strategy['active_strategy'] = new_strategy
223
226
  shared_strategy['pending_strategy'] = None
224
227
  shared_strategy['last_check_time'] = current_time
225
228
  shared_strategy['last_transition_time'] = current_time
226
229
  shared_strategy['session_invalidated'] = True # Invalidate all sessions
230
+
231
+ _strategy_debug_log(f"Strategy transition complete. All sessions invalidated.")
227
232
 
228
233
  return new_strategy
229
234
  return None
@@ -250,6 +255,7 @@ def _register_query_strategy(query_id, strategy):
250
255
  query_map = shared_strategy.get('query_strategy_map', {})
251
256
  query_map[query_id] = normalized_strategy
252
257
  shared_strategy['query_strategy_map'] = query_map
258
+ _strategy_debug_log(f"Query {query_id} registered with strategy: {normalized_strategy}")
253
259
 
254
260
 
255
261
  def _get_query_strategy(query_id):
@@ -271,8 +277,11 @@ def _cleanup_query_strategy(query_id):
271
277
  shared_strategy = _get_shared_strategy()
272
278
  query_map = shared_strategy.get('query_strategy_map', {})
273
279
  if query_id in query_map:
280
+ strategy = query_map[query_id]
274
281
  del query_map[query_id]
275
282
  shared_strategy['query_strategy_map'] = query_map
283
+ remaining_queries = len(query_map)
284
+ _strategy_debug_log(f"Query {query_id} completed (was using {strategy}). Remaining active queries: {remaining_queries}")
276
285
 
277
286
 
278
287
  def _get_strategy_debug_info():
@@ -317,7 +326,9 @@ class Connection(object):
317
326
  cluster_name: str = None,
318
327
  secure: bool = False,
319
328
  auto_resume: bool = True,
329
+ scheme: str = 'e6data',
320
330
  grpc_options: dict = None,
331
+ debug: bool = False,
321
332
  ):
322
333
  """
323
334
  Parameters
@@ -340,6 +351,8 @@ class Connection(object):
340
351
  Flag to use a secure channel for data transfer
341
352
  auto_resume: bool, Optional
342
353
  Flag to enable auto resume of the cluster before the query execution
354
+ scheme: string, Optional
355
+ e6data supported scheme
343
356
  grpc_options: dict, Optional
344
357
  Specify gRPC configuration
345
358
  - keepalive_timeout_ms: This parameter defines the time, in milliseconds, that a gRPC connection should remain idle before sending a keepalive ping to check if the connection is still alive.
@@ -347,6 +360,8 @@ class Connection(object):
347
360
  - 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
348
361
  - grpc_prepare_timeout: Timeout for prepare statement API call (default to 10 minutes).
349
362
  - keepalive_time_ms: This parameter defines the time, in milliseconds, Default to 30 seconds
363
+ debug: bool, Optional
364
+ Flag to enable debug logging for blue-green deployment strategy changes
350
365
  """
351
366
  if not username or not password:
352
367
  raise ValueError("username or password cannot be empty.")
@@ -376,6 +391,13 @@ class Connection(object):
376
391
  The default maximum time on client side to wait for the cluster to resume is 5 minutes.
377
392
  """
378
393
  self.grpc_auto_resume_timeout_seconds = self._grpc_options.pop('grpc_auto_resume_timeout_seconds')
394
+
395
+ # Store debug flag and register with debug connections
396
+ self._debug = debug
397
+ if self._debug:
398
+ _debug_connections.add(id(self))
399
+ _strategy_debug_log(f"Debug mode enabled for connection {id(self)}")
400
+
379
401
  self._create_client()
380
402
 
381
403
  @property
@@ -502,6 +524,7 @@ class Connection(object):
502
524
 
503
525
  if active_strategy and not pending_strategy:
504
526
  # Use cached strategy only if there's no pending strategy
527
+ _strategy_debug_log(f"Authenticating with cached strategy: {active_strategy}")
505
528
  try:
506
529
  authenticate_response = self._client.authenticate(
507
530
  authenticate_request,
@@ -515,10 +538,15 @@ class Connection(object):
515
538
  new_strategy = authenticate_response.new_strategy.lower()
516
539
  if new_strategy != active_strategy:
517
540
  _set_pending_strategy(new_strategy)
518
- # Don't apply immediately - let new queries use fresh connections
541
+ _apply_pending_strategy()
542
+ self._session_id = None
543
+ self.close()
544
+ self._create_client()
545
+ return self.get_session_id
519
546
  except _InactiveRpcError as e:
520
547
  if e.code() == grpc.StatusCode.UNKNOWN and 'status: 456' in e.details():
521
548
  # Strategy changed, clear cache and retry
549
+ _strategy_debug_log(f"Got 456 error with strategy {active_strategy}, clearing cache and retrying")
522
550
  _clear_strategy_cache()
523
551
  active_strategy = None
524
552
  else:
@@ -537,8 +565,10 @@ class Connection(object):
537
565
  else:
538
566
  # Always try blue first, then green if it fails with 456
539
567
  strategies = ['blue', 'green']
568
+ _strategy_debug_log(f"No cached strategy, will try strategies in order: {strategies}")
540
569
  last_error = None
541
570
  for strategy in strategies:
571
+ _strategy_debug_log(f"Attempting authentication with strategy: {strategy}")
542
572
  try:
543
573
  authenticate_response = self._client.authenticate(
544
574
  authenticate_request,
@@ -547,19 +577,24 @@ class Connection(object):
547
577
  self._session_id = authenticate_response.sessionId
548
578
  if self._session_id:
549
579
  # Success! Cache this strategy
580
+ _strategy_debug_log(f"Authentication successful with strategy: {strategy}")
550
581
  _set_active_strategy(strategy)
551
582
 
552
583
  # Check for new strategy in authenticate response
553
- if hasattr(authenticate_response,
554
- 'new_strategy') and authenticate_response.new_strategy:
584
+ if hasattr(authenticate_response, 'new_strategy') and authenticate_response.new_strategy:
555
585
  new_strategy = authenticate_response.new_strategy.lower()
556
586
  if new_strategy != strategy:
557
587
  _set_pending_strategy(new_strategy)
558
- # Don't apply immediately - let new queries use fresh connections
588
+ _apply_pending_strategy()
589
+ self.close()
590
+ self._create_client()
591
+ self._session_id = None
592
+ return self.get_session_id
559
593
  break
560
594
  except _InactiveRpcError as e:
561
595
  if e.code() == grpc.StatusCode.UNKNOWN and 'status: 456' in e.details():
562
596
  # Wrong strategy, try the next one
597
+ _strategy_debug_log(f"Strategy {strategy} failed with 456 error, trying next")
563
598
  last_error = e
564
599
  continue
565
600
  else:
@@ -586,21 +621,6 @@ class Connection(object):
586
621
  ).resume()
587
622
  if status:
588
623
  return self.get_session_id
589
- # authenticate_request = e6x_engine_pb2.AuthenticateRequest(
590
- # user=self.__username,
591
- # password=self.__password
592
- # )
593
- # authenticate_response = self._client.authenticate(
594
- # authenticate_request,
595
- # metadata=_get_grpc_header(cluster=self.cluster_uuid, strategy=_get_active_strategy())
596
- # )
597
- # self._session_id = authenticate_response.sessionId
598
- # # Check for new strategy in authenticate response
599
- # if hasattr(authenticate_response, 'new_strategy') and authenticate_response.new_strategy:
600
- # new_strategy = authenticate_response.new_strategy.lower()
601
- # if new_strategy != _get_active_strategy():
602
- # _set_pending_strategy(new_strategy)
603
- # # Don't apply immediately - let new queries use fresh connections
604
624
  else:
605
625
  raise e
606
626
  else:
@@ -646,6 +666,11 @@ class Connection(object):
646
666
  self._channel.close()
647
667
  self._channel = None
648
668
  self._session_id = None
669
+
670
+ # Remove from debug connections if debug was enabled
671
+ if self._debug:
672
+ _debug_connections.discard(id(self))
673
+ _strategy_debug_log(f"Debug mode disabled for connection {id(self)}")
649
674
 
650
675
  def check_connection(self):
651
676
  """
@@ -1092,6 +1117,7 @@ class Cursor(DBAPICursor):
1092
1117
  query_map = shared_strategy.get('query_strategy_map', {})
1093
1118
 
1094
1119
  if pending_strategy and len(query_map) == 0:
1120
+ _strategy_debug_log(f"Last query cleared, triggering pending strategy transition")
1095
1121
  _apply_pending_strategy()
1096
1122
 
1097
1123
  return clear_response
@@ -1115,6 +1141,7 @@ class Cursor(DBAPICursor):
1115
1141
  query_map = shared_strategy.get('query_strategy_map', {})
1116
1142
 
1117
1143
  if pending_strategy and len(query_map) == 0:
1144
+ _strategy_debug_log(f"Last query cleared, triggering pending strategy transition")
1118
1145
  _apply_pending_strategy()
1119
1146
 
1120
1147
  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.7rc2
3
+ Version: 2.3.9
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.7rc2-blue.svg)
41
+ ![version](https://img.shields.io/badge/version-2.3.9-blue.svg)
42
42
 
43
43
  ## Introduction
44
44
 
@@ -93,6 +93,55 @@ conn = Connection(
93
93
  )
94
94
  ```
95
95
 
96
+ #### Connection Parameters
97
+
98
+ The `Connection` class supports the following parameters:
99
+
100
+ | Parameter | Type | Required | Default | Description |
101
+ |-----------|------|----------|---------|-------------|
102
+ | `host` | str | Yes | - | IP address or hostname of the e6data cluster |
103
+ | `port` | int | Yes | - | Port of the e6data engine (typically 80) |
104
+ | `username` | str | Yes | - | Your e6data Email ID |
105
+ | `password` | str | Yes | - | Access Token generated in the e6data console |
106
+ | `database` | str | No | None | Database to perform queries on |
107
+ | `catalog` | str | No | None | Catalog name |
108
+ | `cluster_name` | str | No | None | Name of the cluster for cluster-specific operations |
109
+ | `secure` | bool | No | False | Enable SSL/TLS for secure connections |
110
+ | `auto_resume` | bool | No | True | Automatically resume cluster if suspended |
111
+ | `grpc_options` | dict | No | None | Additional gRPC configuration options |
112
+
113
+ #### Secure Connection Example
114
+
115
+ To establish a secure connection using SSL/TLS:
116
+
117
+ ```python
118
+ conn = Connection(
119
+ host=host,
120
+ port=443, # Typically 443 for secure connections
121
+ username=username,
122
+ password=password,
123
+ database=database,
124
+ cluster_name='production-cluster',
125
+ secure=True # Enable SSL/TLS
126
+ )
127
+ ```
128
+
129
+ #### Cluster-Specific Connection
130
+
131
+ When working with multiple clusters, specify the cluster name:
132
+
133
+ ```python
134
+ conn = Connection(
135
+ host=host,
136
+ port=port,
137
+ username=username,
138
+ password=password,
139
+ database=database,
140
+ cluster_name='analytics-cluster-01', # Specify cluster name
141
+ secure=True
142
+ )
143
+ ```
144
+
96
145
  ### Perform a Queries & Get Results
97
146
 
98
147
  ```python
@@ -4,10 +4,10 @@ e6data_python_connector/common.py,sha256=nk0CDQ5j1iu8DctBOXX4QypPYmxSgIgoA-Cn1UZ
4
4
  e6data_python_connector/constants.py,sha256=h5w0lWiFsxz__zUZRlo1f72-8mT_vt9gcWvtWh0viFI,682
5
5
  e6data_python_connector/datainputstream.py,sha256=nHQmb0kAdNJWSubhJ6M156EZjBjImpiQOGKlxXC0DnA,25841
6
6
  e6data_python_connector/date_time_utils.py,sha256=zmIwRhEGJwDvCOUjAl6VTDWGppluA-DPl_ioHTNpJKI,14413
7
- e6data_python_connector/dialect.py,sha256=4GHNzbbOULhXqi9WPJ7oz-d8aLVze41Zp4uSQ43QAv8,11626
8
- e6data_python_connector/e6data_grpc.py,sha256=bKmtPaJfCZv395njTjJSOem8kVHCfTkL-nsNtcPqMWc,57701
7
+ e6data_python_connector/dialect.py,sha256=oj-vyH_P6RQcRzRgDHN8dTwTQpkB2meaNMvykWpdTzc,11741
8
+ e6data_python_connector/e6data_grpc.py,sha256=9loOth3-7d8T7VWUIXUVGkCBfdSizy4OtgvaAfd79VI,59208
9
9
  e6data_python_connector/exceptions.py,sha256=vrUqdzMq3LjbCmvr5_TcRq9eKmdqGfe-uVjR0BJQf5U,382
10
- e6data_python_connector/strategy.py,sha256=bv01noiP6TN80LBWeuRw1HoMaCacFP1rDWOxktIo11w,7334
10
+ e6data_python_connector/strategy.py,sha256=whAC-C031XFirP71hXKqdiMTJRIUUEvc34tuo2W8k-0,6539
11
11
  e6data_python_connector/typeId.py,sha256=2EMNWK2SO5CBReNuJphma6xp-69r9GDwwys66t-kSk0,1777
12
12
  e6data_python_connector/cluster_server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  e6data_python_connector/cluster_server/cluster_pb2.py,sha256=BWxO5vG1SVkx2y-i6A6wvP5sfb8KNKosyl2N5lIsVd8,2339
@@ -62,9 +62,9 @@ test/tests_grpc.py,sha256=VRqJwW2BVRpL3c0BnwCKvtowogAS_0JYa1Ss4ynqTss,5916
62
62
  test/validate_decimal128.py,sha256=8m7FFD3nOHFkPKpN1kCd5MDDzfeEEJnMnOoSeXmX_I0,3320
63
63
  test/validate_implementation.py,sha256=2keEodBGxztdG1kgavTq-p-cLw1L9UNQLkXTfIijBNQ,5569
64
64
  test/verify_decimal_fix.py,sha256=n7XUV4dbo7kTdx-mVqhha9Nr6_bkDudwplHTdV-rM_Y,1051
65
- e6data_python_connector-2.3.7rc2.dist-info/LICENSE,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
66
- e6data_python_connector-2.3.7rc2.dist-info/METADATA,sha256=G5Ict2zfOSkxDqnJfZaIWR4FzW0DhULXhQMFr4TM5hA,12777
67
- e6data_python_connector-2.3.7rc2.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
68
- e6data_python_connector-2.3.7rc2.dist-info/entry_points.txt,sha256=MDK4cAOND0preLvq2Tlpnz00wXnP_Y_UopqJsAf3U7Y,77
69
- e6data_python_connector-2.3.7rc2.dist-info/top_level.txt,sha256=uOWV7xMMlZxe030EIU4bGUuKJ-W78lKxX4YHA7YFclw,29
70
- e6data_python_connector-2.3.7rc2.dist-info/RECORD,,
65
+ e6data_python_connector-2.3.9.dist-info/LICENSE,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
66
+ e6data_python_connector-2.3.9.dist-info/METADATA,sha256=QKpmS6oLS0597Tnzui5eB2bXAX_NzFgaFcp-1Iqqrv8,14325
67
+ e6data_python_connector-2.3.9.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
68
+ e6data_python_connector-2.3.9.dist-info/entry_points.txt,sha256=MDK4cAOND0preLvq2Tlpnz00wXnP_Y_UopqJsAf3U7Y,77
69
+ e6data_python_connector-2.3.9.dist-info/top_level.txt,sha256=uOWV7xMMlZxe030EIU4bGUuKJ-W78lKxX4YHA7YFclw,29
70
+ e6data_python_connector-2.3.9.dist-info/RECORD,,