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.
- {e6data_python_connector-2.3.11rc1/e6data_python_connector.egg-info → e6data_python_connector-2.3.11rc2}/PKG-INFO +2 -2
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/README.md +1 -1
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/cluster_manager.py +93 -9
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/datainputstream.py +19 -31
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/e6data_grpc.py +39 -52
- {e6data_python_connector-2.3.11rc1/gen-py → e6data_python_connector-2.3.11rc2/e6data_python_connector}/e6x_vector/ttypes.py +2 -26
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2/e6data_python_connector.egg-info}/PKG-INFO +2 -2
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/setup.py +1 -1
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/LICENSE +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/MANIFEST.in +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/__init__.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/cluster_server/__init__.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/cluster_server/cluster_pb2.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/cluster_server/cluster_pb2_grpc.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/common.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/connection_pool.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/constants.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/date_time_utils.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/dialect.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/e6x_vector/__init__.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/e6x_vector/constants.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/exceptions.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/server/QueryEngineService.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/server/__init__.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/server/constants.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/server/e6x_engine_pb2.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/server/e6x_engine_pb2.pyi +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/server/e6x_engine_pb2_grpc.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/server/ttypes.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/strategy.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector/typeId.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector.egg-info/SOURCES.txt +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector.egg-info/dependency_links.txt +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector.egg-info/entry_points.txt +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector.egg-info/requires.txt +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/e6data_python_connector.egg-info/top_level.txt +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/gen-py/__init__.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/gen-py/e6x_vector/__init__.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/gen-py/e6x_vector/constants.py +0 -0
- {e6data_python_connector-2.3.11rc1/e6data_python_connector → e6data_python_connector-2.3.11rc2/gen-py}/e6x_vector/ttypes.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/pyproject.toml +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/setup.cfg +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/__init__.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/analyze_38_nines.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/analyze_all_cases.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/analyze_binary.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/analyze_correct_value.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/analyze_fields.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/check_decimal_errors.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/cleanup_test_files.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/debug_38_nines.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/debug_binary.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/final_test.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/move_tests.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/quick_test.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_38_nines.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_all_decimal128_cases.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_cluster_manager_efficiency.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_cluster_manager_none_strategy.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_cluster_manager_strategy.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_comprehensive.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_connection_pool.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_connection_pool_e2e.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_current_implementation.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_decimal128_binary_parsing.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_decimal128_parsing.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_fix.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_improved_parsing.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_known_case.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_manual_analysis.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_mock_server.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_multiprocessing_fix.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_new_implementation.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_pool_concurrency_simple.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_pool_threading_multiprocessing.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_simple_connection_pool.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_specific_binary.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_strategy.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_strategy_logic.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_strategy_persistence_fix.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_strategy_sharing_fix.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_user_binary_value.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/tests.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/tests_grpc.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/validate_decimal128.py +0 -0
- {e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/validate_implementation.py +0 -0
- {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.
|
|
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
|
-

|
|
43
43
|
|
|
44
44
|
## Introduction
|
|
45
45
|
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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'
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
110
|
-
#
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
122
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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"
|
|
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
|
-
|
|
630
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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.
|
|
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
|
-

|
|
43
43
|
|
|
44
44
|
## Introduction
|
|
45
45
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/analyze_38_nines.py
RENAMED
|
File without changes
|
{e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/analyze_all_cases.py
RENAMED
|
File without changes
|
{e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/analyze_binary.py
RENAMED
|
File without changes
|
|
File without changes
|
{e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/analyze_fields.py
RENAMED
|
File without changes
|
{e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/check_decimal_errors.py
RENAMED
|
File without changes
|
{e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/cleanup_test_files.py
RENAMED
|
File without changes
|
{e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/debug_38_nines.py
RENAMED
|
File without changes
|
{e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/debug_binary.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_38_nines.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_comprehensive.py
RENAMED
|
File without changes
|
{e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_connection_pool.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_known_case.py
RENAMED
|
File without changes
|
{e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_manual_analysis.py
RENAMED
|
File without changes
|
{e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_mock_server.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_specific_binary.py
RENAMED
|
File without changes
|
{e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_strategy.py
RENAMED
|
File without changes
|
{e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/test_strategy_logic.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/validate_decimal128.py
RENAMED
|
File without changes
|
|
File without changes
|
{e6data_python_connector-2.3.11rc1 → e6data_python_connector-2.3.11rc2}/test/verify_decimal_fix.py
RENAMED
|
File without changes
|