e6data-python-connector 3.0.1rc4__tar.gz → 3.0.3rc1__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-3.0.1rc4/e6data_python_connector.egg-info → e6data_python_connector-3.0.3rc1}/PKG-INFO +1 -1
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector/async_cluster_manager.py +7 -0
- e6data_python_connector-3.0.3rc1/e6data_python_connector/calendar_interval.py +29 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector/cluster_manager.py +2 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector/datainputstream.py +26 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector/e6data_grpc.py +8 -7
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector/e6x_vector/ttypes.py +136 -1
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1/e6data_python_connector.egg-info}/PKG-INFO +1 -1
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector.egg-info/SOURCES.txt +2 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/setup.py +1 -1
- e6data_python_connector-3.0.3rc1/test/integration/test_cluster_resume_errors.py +179 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/LICENSE +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/MANIFEST.in +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/README.md +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector/__init__.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector/aio.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector/async_connection.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector/async_connection_pool.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector/async_cursor.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector/async_dialect.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector/async_oauth.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector/async_work.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector/cluster_server/__init__.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector/cluster_server/cluster_pb2.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector/cluster_server/cluster_pb2_grpc.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector/common.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector/connection_pool.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector/constants.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector/date_time_utils.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector/dialect.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector/e6x_vector/__init__.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector/e6x_vector/constants.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector/exceptions.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector/oauth.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector/oauth_cache.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector/oauth_common.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector/result_batch.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector/result_decode.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector/result_decode_worker.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector/result_prefetch.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector/server/QueryEngineService.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector/server/__init__.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector/server/constants.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector/server/e6x_engine_pb2.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector/server/e6x_engine_pb2.pyi +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector/server/e6x_engine_pb2_grpc.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector/server/ttypes.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector/strategy.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector/typeId.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector.egg-info/dependency_links.txt +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector.egg-info/entry_points.txt +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector.egg-info/requires.txt +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector.egg-info/top_level.txt +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/pyproject.toml +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/setup.cfg +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/__init__.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/analyze_38_nines.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/analyze_all_cases.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/analyze_binary.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/analyze_correct_value.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/analyze_fields.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/check_decimal_errors.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/cleanup_test_files.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/conftest.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/debug_38_nines.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/debug_binary.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/final_test.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/integration/__init__.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/integration/benchmark_preflight.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/integration/benchmark_result_batch_v2.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/integration/benchmark_sync_async.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/integration/expiry_preflight.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/integration/result_batch_benchmark_support.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/integration/support.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/integration/test_async_connection.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/integration/test_async_pool.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/integration/test_async_queries.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/integration/test_async_resume.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/integration/test_async_sqlalchemy.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/integration/test_async_token_lifecycle.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/integration/test_e2e_auth_boundary.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/integration/test_e2e_oauth_continuation.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/integration/test_e2e_readonly_matrix.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/integration/test_result_batch_v2.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/integration/test_shared_token_issuance.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/integration/test_sync_oauth_lifecycle.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/integration/token_observation.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/move_tests.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/quick_test.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_38_nines.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_all_decimal128_cases.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_cluster_manager_efficiency.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_cluster_manager_none_strategy.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_cluster_manager_oauth.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_cluster_manager_strategy.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_code_optimizations.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_comprehensive.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_connection_pool.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_connection_pool_e2e.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_current_implementation.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_decimal128_binary_parsing.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_decimal128_parsing.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_dialect_columns.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_fix.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_improved_parsing.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_known_case.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_manual_analysis.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_mock_server.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_multiprocessing_fix.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_new_implementation.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_oauth_authentication.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_oauth_auto_resume.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_oauth_resume_integration.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_pool_concurrency_simple.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_pool_threading_multiprocessing.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_simple_connection_pool.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_specific_binary.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_strategy.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_strategy_logic.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_strategy_persistence_fix.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_strategy_sharing_fix.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_user_binary_value.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/tests.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/tests_grpc.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/validate_decimal128.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/validate_implementation.py +0 -0
- {e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/verify_decimal_fix.py +0 -0
|
@@ -50,6 +50,9 @@ class _ResumeState:
|
|
|
50
50
|
def acknowledged(self):
|
|
51
51
|
self.pending = 'acknowledged'
|
|
52
52
|
|
|
53
|
+
def rejected(self):
|
|
54
|
+
self.pending = None
|
|
55
|
+
|
|
53
56
|
|
|
54
57
|
class _ResumeFlight:
|
|
55
58
|
"""One phase deadline, independently bounded shielded waiters."""
|
|
@@ -167,6 +170,8 @@ class AsyncClusterManager:
|
|
|
167
170
|
_remaining(deadline)
|
|
168
171
|
return await self._flight.wait(self._recover, deadline)
|
|
169
172
|
except (TimeoutError, grpc.RpcError) as error:
|
|
173
|
+
if isinstance(error, grpc.RpcError) and error.code() == grpc.StatusCode.FAILED_PRECONDITION:
|
|
174
|
+
raise OperationalError('Cluster recovery failed: {}'.format(error.details())) from error
|
|
170
175
|
raise OperationalError('Cluster recovery failed; a dispatched resume may still complete.') from error
|
|
171
176
|
|
|
172
177
|
async def _metadata(self, strategy, deadline, rejected_token=None):
|
|
@@ -199,6 +204,8 @@ class AsyncClusterManager:
|
|
|
199
204
|
self.last_successful_strategy = strategy
|
|
200
205
|
return response
|
|
201
206
|
except grpc.RpcError as error:
|
|
207
|
+
if operation == 'resume' and error.code() == grpc.StatusCode.FAILED_PRECONDITION:
|
|
208
|
+
self._mutation.rejected()
|
|
202
209
|
if (error.code() == grpc.StatusCode.UNAUTHENTICATED and not refreshed
|
|
203
210
|
and self._metadata_provider is not None):
|
|
204
211
|
refreshed = True
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Lossless calendar intervals returned by MAKE_INTERVAL."""
|
|
2
|
+
from typing import NamedTuple
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class CalendarInterval(NamedTuple):
|
|
6
|
+
"""Months and calendar days are independent of elapsed microseconds."""
|
|
7
|
+
|
|
8
|
+
months: int
|
|
9
|
+
days: int
|
|
10
|
+
microseconds: int
|
|
11
|
+
|
|
12
|
+
def __str__(self):
|
|
13
|
+
month_sign = -1 if self.months < 0 else 1
|
|
14
|
+
time_sign = -1 if self.microseconds < 0 else 1
|
|
15
|
+
months = abs(self.months)
|
|
16
|
+
micros = abs(self.microseconds)
|
|
17
|
+
values = (month_sign * (months // 12), month_sign * (months % 12),
|
|
18
|
+
self.days, time_sign * (micros // 3600000000),
|
|
19
|
+
time_sign * (micros // 60000000 % 60))
|
|
20
|
+
parts = [f"{value} {unit}" for value, unit in
|
|
21
|
+
zip(values, ("years", "months", "days", "hours", "minutes")) if value]
|
|
22
|
+
seconds = micros % 60000000
|
|
23
|
+
if seconds:
|
|
24
|
+
whole, fraction = divmod(seconds, 1000000)
|
|
25
|
+
text = str(whole)
|
|
26
|
+
if fraction:
|
|
27
|
+
text += f".{fraction:06d}".rstrip('0')
|
|
28
|
+
parts.append(f"{'-' if time_sign < 0 else ''}{text} seconds")
|
|
29
|
+
return ' '.join(parts) if parts else '0 seconds'
|
|
@@ -571,6 +571,8 @@ class ClusterManager:
|
|
|
571
571
|
except _InactiveRpcError as e:
|
|
572
572
|
if self._debug:
|
|
573
573
|
logger.error(f"Failed to send resume request: {e}")
|
|
574
|
+
if e.code() == grpc.StatusCode.FAILED_PRECONDITION:
|
|
575
|
+
raise
|
|
574
576
|
return False
|
|
575
577
|
elif current_status.status == 'active':
|
|
576
578
|
if self._debug:
|
|
@@ -10,6 +10,7 @@ from thrift.transport import TTransport
|
|
|
10
10
|
|
|
11
11
|
from e6data_python_connector.e6x_vector.ttypes import Chunk, Vector, VectorType
|
|
12
12
|
from e6data_python_connector.constants import ZONE
|
|
13
|
+
from e6data_python_connector.calendar_interval import CalendarInterval
|
|
13
14
|
from e6data_python_connector.date_time_utils import (
|
|
14
15
|
floor_div,
|
|
15
16
|
floor_mod,
|
|
@@ -478,6 +479,10 @@ def read_values_from_array(query_columns_description: list, dis: DataInputStream
|
|
|
478
479
|
try:
|
|
479
480
|
if dtype == "LONG":
|
|
480
481
|
value_array.append(dis.read_long())
|
|
482
|
+
elif dtype == "CALENDAR_INTERVAL":
|
|
483
|
+
value_array.append(CalendarInterval(dis.read_int(), dis.read_int(), dis.read_long()))
|
|
484
|
+
elif dtype == "INTERVAL_DAY_SECOND":
|
|
485
|
+
value_array.append(timedelta(microseconds=dis.read_long()))
|
|
481
486
|
elif dtype == "DATE":
|
|
482
487
|
value_array.append(format_iso_date_from_epoch_micros(dis.read_long()))
|
|
483
488
|
elif dtype == "DATETIME":
|
|
@@ -582,6 +587,27 @@ def get_column_from_chunk(vector: Vector, strict=False) -> list:
|
|
|
582
587
|
continue
|
|
583
588
|
value_array.append(vector.data.int64Data.data[
|
|
584
589
|
row] if not vector.isConstantVector else vector.data.numericConstantData.data)
|
|
590
|
+
elif d_type == VectorType.CALENDAR_INTERVAL:
|
|
591
|
+
payload = getattr(vector.data, 'calendarIntervalData', None)
|
|
592
|
+
for row in range(vector.size):
|
|
593
|
+
if get_null(vector, row):
|
|
594
|
+
value_array.append(None)
|
|
595
|
+
continue
|
|
596
|
+
index = 0 if vector.isConstantVector else row
|
|
597
|
+
components = (payload.months, payload.days, payload.microseconds) if payload else ()
|
|
598
|
+
if not components or any(values is None or index >= len(values) or values[index] is None
|
|
599
|
+
for values in components):
|
|
600
|
+
raise ValueError("Incomplete CALENDAR_INTERVAL vector payload")
|
|
601
|
+
value_array.append(CalendarInterval(*(values[index] for values in components)))
|
|
602
|
+
elif d_type == VectorType.INTERVAL_DAY_SECOND:
|
|
603
|
+
for row in range(vector.size):
|
|
604
|
+
if get_null(vector, row):
|
|
605
|
+
value_array.append(None)
|
|
606
|
+
continue
|
|
607
|
+
microseconds = (vector.data.int64Data.data[row]
|
|
608
|
+
if not vector.isConstantVector
|
|
609
|
+
else vector.data.numericConstantData.data)
|
|
610
|
+
value_array.append(timedelta(microseconds=microseconds))
|
|
585
611
|
elif d_type == VectorType.DATE:
|
|
586
612
|
# Use the JDBC-parity formatter so years > 9999 emit "+YYYYY-MM-DD"
|
|
587
613
|
# instead of raising. Per-row try/except keeps a single bad value
|
|
@@ -521,17 +521,18 @@ class Connection(object):
|
|
|
521
521
|
if enable_result_batch_v2:
|
|
522
522
|
from .result_decode import validate_decode_runtime
|
|
523
523
|
validate_decode_runtime()
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
524
|
+
normalized = {}
|
|
525
|
+
for key, value in (grpc_options or {}).items():
|
|
526
|
+
key = key[5:] if key.startswith('grpc.') else key
|
|
527
|
+
if key in normalized and normalized[key] != value:
|
|
528
|
+
raise ValueError('Conflicting gRPC options after prefix normalization.')
|
|
529
|
+
normalized[key] = value
|
|
530
|
+
grpc_options = normalized
|
|
531
|
+
if enable_result_batch_v2:
|
|
530
532
|
limit = normalized.get('max_receive_message_length', -1)
|
|
531
533
|
if isinstance(limit, bool) or not isinstance(limit, int) or (limit != -1 and limit <= 0):
|
|
532
534
|
raise ValueError('V2 receive limit must be a positive integer or -1 for unlimited.')
|
|
533
535
|
normalized['max_receive_message_length'] = limit
|
|
534
|
-
grpc_options = normalized
|
|
535
536
|
if enable_result_batch_v2 and grpc_options is not None and 'grpc_prepare_timeout' in grpc_options:
|
|
536
537
|
timeout = grpc_options['grpc_prepare_timeout']
|
|
537
538
|
if (isinstance(timeout, bool) or not isinstance(timeout, (int, float))
|
|
@@ -35,6 +35,8 @@ class VectorType(object):
|
|
|
35
35
|
NULL = 14
|
|
36
36
|
TIMESTAMP_TZ = 15
|
|
37
37
|
DECIMAL128 = 16
|
|
38
|
+
INTERVAL_DAY_SECOND = 17
|
|
39
|
+
CALENDAR_INTERVAL = 18
|
|
38
40
|
|
|
39
41
|
_VALUES_TO_NAMES = {
|
|
40
42
|
0: "BINARY",
|
|
@@ -54,6 +56,8 @@ class VectorType(object):
|
|
|
54
56
|
14: "NULL",
|
|
55
57
|
15: "TIMESTAMP_TZ",
|
|
56
58
|
16: "DECIMAL128",
|
|
59
|
+
17: "INTERVAL_DAY_SECOND",
|
|
60
|
+
18: "CALENDAR_INTERVAL",
|
|
57
61
|
}
|
|
58
62
|
|
|
59
63
|
_NAMES_TO_VALUES = {
|
|
@@ -74,6 +78,8 @@ class VectorType(object):
|
|
|
74
78
|
"NULL": 14,
|
|
75
79
|
"TIMESTAMP_TZ": 15,
|
|
76
80
|
"DECIMAL128": 16,
|
|
81
|
+
"INTERVAL_DAY_SECOND": 17,
|
|
82
|
+
"CALENDAR_INTERVAL": 18,
|
|
77
83
|
}
|
|
78
84
|
|
|
79
85
|
|
|
@@ -232,12 +238,13 @@ class Data(object):
|
|
|
232
238
|
- timeData
|
|
233
239
|
- decimal128Data
|
|
234
240
|
- numericDecimal128ConstantData
|
|
241
|
+
- calendarIntervalData
|
|
235
242
|
|
|
236
243
|
"""
|
|
237
244
|
thrift_spec = None
|
|
238
245
|
|
|
239
246
|
|
|
240
|
-
def __init__(self, boolData = None, int32Data = None, int64Data = None, dateData = None, float32Data = None, float64Data = None, varcharData = None, boolConstantData = None, dateConstantData = None, nullConstantData = None, numericConstantData = None, numericDecimalConstantData = None, temporalIntervalConstantData = None, timeConstantData = None, varcharConstantData = None, timeData = None, decimal128Data = None, numericDecimal128ConstantData = None,):
|
|
247
|
+
def __init__(self, boolData = None, int32Data = None, int64Data = None, dateData = None, float32Data = None, float64Data = None, varcharData = None, boolConstantData = None, dateConstantData = None, nullConstantData = None, numericConstantData = None, numericDecimalConstantData = None, temporalIntervalConstantData = None, timeConstantData = None, varcharConstantData = None, timeData = None, decimal128Data = None, numericDecimal128ConstantData = None, calendarIntervalData = None,):
|
|
241
248
|
self.boolData = boolData
|
|
242
249
|
self.int32Data = int32Data
|
|
243
250
|
self.int64Data = int64Data
|
|
@@ -256,6 +263,7 @@ class Data(object):
|
|
|
256
263
|
self.timeData = timeData
|
|
257
264
|
self.decimal128Data = decimal128Data
|
|
258
265
|
self.numericDecimal128ConstantData = numericDecimal128ConstantData
|
|
266
|
+
self.calendarIntervalData = calendarIntervalData
|
|
259
267
|
|
|
260
268
|
def read(self, iprot):
|
|
261
269
|
if iprot._fast_decode is not None and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None:
|
|
@@ -374,6 +382,12 @@ class Data(object):
|
|
|
374
382
|
self.numericDecimal128ConstantData.read(iprot)
|
|
375
383
|
else:
|
|
376
384
|
iprot.skip(ftype)
|
|
385
|
+
elif fid == 23:
|
|
386
|
+
if ftype == TType.STRUCT:
|
|
387
|
+
self.calendarIntervalData = CalendarIntervalData()
|
|
388
|
+
self.calendarIntervalData.read(iprot)
|
|
389
|
+
else:
|
|
390
|
+
iprot.skip(ftype)
|
|
377
391
|
else:
|
|
378
392
|
iprot.skip(ftype)
|
|
379
393
|
iprot.readFieldEnd()
|
|
@@ -457,6 +471,10 @@ class Data(object):
|
|
|
457
471
|
oprot.writeFieldBegin('numericDecimal128ConstantData', TType.STRUCT, 18)
|
|
458
472
|
self.numericDecimal128ConstantData.write(oprot)
|
|
459
473
|
oprot.writeFieldEnd()
|
|
474
|
+
if self.calendarIntervalData is not None:
|
|
475
|
+
oprot.writeFieldBegin('calendarIntervalData', TType.STRUCT, 23)
|
|
476
|
+
self.calendarIntervalData.write(oprot)
|
|
477
|
+
oprot.writeFieldEnd()
|
|
460
478
|
oprot.writeFieldStop()
|
|
461
479
|
oprot.writeStructEnd()
|
|
462
480
|
|
|
@@ -1738,6 +1756,111 @@ class Chunk(object):
|
|
|
1738
1756
|
|
|
1739
1757
|
def __ne__(self, other):
|
|
1740
1758
|
return not (self == other)
|
|
1759
|
+
|
|
1760
|
+
|
|
1761
|
+
class CalendarIntervalData(object):
|
|
1762
|
+
"""
|
|
1763
|
+
Attributes:
|
|
1764
|
+
- months
|
|
1765
|
+
- days
|
|
1766
|
+
- microseconds
|
|
1767
|
+
|
|
1768
|
+
"""
|
|
1769
|
+
thrift_spec = None
|
|
1770
|
+
|
|
1771
|
+
|
|
1772
|
+
def __init__(self, months = None, days = None, microseconds = None,):
|
|
1773
|
+
self.months = months
|
|
1774
|
+
self.days = days
|
|
1775
|
+
self.microseconds = microseconds
|
|
1776
|
+
|
|
1777
|
+
def read(self, iprot):
|
|
1778
|
+
if iprot._fast_decode is not None and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None:
|
|
1779
|
+
iprot._fast_decode(self, iprot, [self.__class__, self.thrift_spec])
|
|
1780
|
+
return
|
|
1781
|
+
iprot.readStructBegin()
|
|
1782
|
+
while True:
|
|
1783
|
+
(fname, ftype, fid) = iprot.readFieldBegin()
|
|
1784
|
+
if ftype == TType.STOP:
|
|
1785
|
+
break
|
|
1786
|
+
if fid == 1:
|
|
1787
|
+
if ftype == TType.LIST:
|
|
1788
|
+
self.months = []
|
|
1789
|
+
(_etype87, _size84) = iprot.readListBegin()
|
|
1790
|
+
for _i88 in range(_size84):
|
|
1791
|
+
_elem89 = iprot.readI32()
|
|
1792
|
+
self.months.append(_elem89)
|
|
1793
|
+
iprot.readListEnd()
|
|
1794
|
+
else:
|
|
1795
|
+
iprot.skip(ftype)
|
|
1796
|
+
elif fid == 2:
|
|
1797
|
+
if ftype == TType.LIST:
|
|
1798
|
+
self.days = []
|
|
1799
|
+
(_etype93, _size90) = iprot.readListBegin()
|
|
1800
|
+
for _i94 in range(_size90):
|
|
1801
|
+
_elem95 = iprot.readI32()
|
|
1802
|
+
self.days.append(_elem95)
|
|
1803
|
+
iprot.readListEnd()
|
|
1804
|
+
else:
|
|
1805
|
+
iprot.skip(ftype)
|
|
1806
|
+
elif fid == 3:
|
|
1807
|
+
if ftype == TType.LIST:
|
|
1808
|
+
self.microseconds = []
|
|
1809
|
+
(_etype99, _size96) = iprot.readListBegin()
|
|
1810
|
+
for _i100 in range(_size96):
|
|
1811
|
+
_elem101 = iprot.readI64()
|
|
1812
|
+
self.microseconds.append(_elem101)
|
|
1813
|
+
iprot.readListEnd()
|
|
1814
|
+
else:
|
|
1815
|
+
iprot.skip(ftype)
|
|
1816
|
+
else:
|
|
1817
|
+
iprot.skip(ftype)
|
|
1818
|
+
iprot.readFieldEnd()
|
|
1819
|
+
iprot.readStructEnd()
|
|
1820
|
+
|
|
1821
|
+
def write(self, oprot):
|
|
1822
|
+
self.validate()
|
|
1823
|
+
if oprot._fast_encode is not None and self.thrift_spec is not None:
|
|
1824
|
+
oprot.trans.write(oprot._fast_encode(self, [self.__class__, self.thrift_spec]))
|
|
1825
|
+
return
|
|
1826
|
+
oprot.writeStructBegin('CalendarIntervalData')
|
|
1827
|
+
if self.months is not None:
|
|
1828
|
+
oprot.writeFieldBegin('months', TType.LIST, 1)
|
|
1829
|
+
oprot.writeListBegin(TType.I32, len(self.months))
|
|
1830
|
+
for iter102 in self.months:
|
|
1831
|
+
oprot.writeI32(iter102)
|
|
1832
|
+
oprot.writeListEnd()
|
|
1833
|
+
oprot.writeFieldEnd()
|
|
1834
|
+
if self.days is not None:
|
|
1835
|
+
oprot.writeFieldBegin('days', TType.LIST, 2)
|
|
1836
|
+
oprot.writeListBegin(TType.I32, len(self.days))
|
|
1837
|
+
for iter103 in self.days:
|
|
1838
|
+
oprot.writeI32(iter103)
|
|
1839
|
+
oprot.writeListEnd()
|
|
1840
|
+
oprot.writeFieldEnd()
|
|
1841
|
+
if self.microseconds is not None:
|
|
1842
|
+
oprot.writeFieldBegin('microseconds', TType.LIST, 3)
|
|
1843
|
+
oprot.writeListBegin(TType.I64, len(self.microseconds))
|
|
1844
|
+
for iter104 in self.microseconds:
|
|
1845
|
+
oprot.writeI64(iter104)
|
|
1846
|
+
oprot.writeListEnd()
|
|
1847
|
+
oprot.writeFieldEnd()
|
|
1848
|
+
oprot.writeFieldStop()
|
|
1849
|
+
oprot.writeStructEnd()
|
|
1850
|
+
|
|
1851
|
+
def validate(self):
|
|
1852
|
+
return
|
|
1853
|
+
|
|
1854
|
+
def __repr__(self):
|
|
1855
|
+
L = ['%s=%r' % (key, value)
|
|
1856
|
+
for key, value in self.__dict__.items()]
|
|
1857
|
+
return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
|
|
1858
|
+
|
|
1859
|
+
def __eq__(self, other):
|
|
1860
|
+
return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
|
|
1861
|
+
|
|
1862
|
+
def __ne__(self, other):
|
|
1863
|
+
return not (self == other)
|
|
1741
1864
|
all_structs.append(Vector)
|
|
1742
1865
|
Vector.thrift_spec = (
|
|
1743
1866
|
None, # 0
|
|
@@ -1770,6 +1893,11 @@ Data.thrift_spec = (
|
|
|
1770
1893
|
(16, TType.STRUCT, 'timeData', [TimeData, None], None, ), # 16
|
|
1771
1894
|
(17, TType.STRUCT, 'decimal128Data', [Decimal128Data, None], None, ), # 17
|
|
1772
1895
|
(18, TType.STRUCT, 'numericDecimal128ConstantData', [NumericDecimal128ConstantData, None], None, ), # 18
|
|
1896
|
+
None, # 19
|
|
1897
|
+
None, # 20
|
|
1898
|
+
None, # 21
|
|
1899
|
+
None, # 22
|
|
1900
|
+
(23, TType.STRUCT, 'calendarIntervalData', [CalendarIntervalData, None], None, ), # 23
|
|
1773
1901
|
)
|
|
1774
1902
|
all_structs.append(BoolData)
|
|
1775
1903
|
BoolData.thrift_spec = (
|
|
@@ -1871,5 +1999,12 @@ Chunk.thrift_spec = (
|
|
|
1871
1999
|
(1, TType.I32, 'size', None, None, ), # 1
|
|
1872
2000
|
(2, TType.LIST, 'vectors', (TType.STRUCT, [Vector, None], False), None, ), # 2
|
|
1873
2001
|
)
|
|
2002
|
+
all_structs.append(CalendarIntervalData)
|
|
2003
|
+
CalendarIntervalData.thrift_spec = (
|
|
2004
|
+
None, # 0
|
|
2005
|
+
(1, TType.LIST, 'months', (TType.I32, None, False), None, ), # 1
|
|
2006
|
+
(2, TType.LIST, 'days', (TType.I32, None, False), None, ), # 2
|
|
2007
|
+
(3, TType.LIST, 'microseconds', (TType.I64, None, False), None, ), # 3
|
|
2008
|
+
)
|
|
1874
2009
|
fix_spec(all_structs)
|
|
1875
2010
|
del all_structs
|
|
@@ -13,6 +13,7 @@ e6data_python_connector/async_cursor.py
|
|
|
13
13
|
e6data_python_connector/async_dialect.py
|
|
14
14
|
e6data_python_connector/async_oauth.py
|
|
15
15
|
e6data_python_connector/async_work.py
|
|
16
|
+
e6data_python_connector/calendar_interval.py
|
|
16
17
|
e6data_python_connector/cluster_manager.py
|
|
17
18
|
e6data_python_connector/common.py
|
|
18
19
|
e6data_python_connector/connection_pool.py
|
|
@@ -115,6 +116,7 @@ test/integration/test_async_queries.py
|
|
|
115
116
|
test/integration/test_async_resume.py
|
|
116
117
|
test/integration/test_async_sqlalchemy.py
|
|
117
118
|
test/integration/test_async_token_lifecycle.py
|
|
119
|
+
test/integration/test_cluster_resume_errors.py
|
|
118
120
|
test/integration/test_e2e_auth_boundary.py
|
|
119
121
|
test/integration/test_e2e_oauth_continuation.py
|
|
120
122
|
test/integration/test_e2e_readonly_matrix.py
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""Real RPC regressions on an explicitly prepared, exclusive-use cluster.
|
|
2
|
+
|
|
3
|
+
The operator must suspend the cluster and disable auto-resume before opting in.
|
|
4
|
+
These tests observe real requests and never alter responses or cluster settings.
|
|
5
|
+
"""
|
|
6
|
+
import asyncio
|
|
7
|
+
import os
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import grpc
|
|
11
|
+
import pytest
|
|
12
|
+
|
|
13
|
+
from e6data_python_connector import Connection
|
|
14
|
+
from e6data_python_connector.aio import AsyncConnection
|
|
15
|
+
from e6data_python_connector.async_cluster_manager import AsyncClusterManager
|
|
16
|
+
from e6data_python_connector.cluster_manager import ClusterManager
|
|
17
|
+
from e6data_python_connector.exceptions import OperationalError
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
pytestmark = pytest.mark.integration
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@pytest.fixture
|
|
24
|
+
def rejected_resume_config(live_config):
|
|
25
|
+
if os.environ.get('E6_RESUME_REJECTION_AUTHORIZED') != '1':
|
|
26
|
+
pytest.skip('Requires an exclusive suspended target with auto-resume disabled')
|
|
27
|
+
if not live_config.connection_kwargs.get('password'):
|
|
28
|
+
pytest.skip('This regression requires the legacy password authentication path')
|
|
29
|
+
return live_config
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _manager_options(config):
|
|
33
|
+
settings = config.connection_kwargs
|
|
34
|
+
return dict(host=settings['host'], port=settings['port'],
|
|
35
|
+
user=settings['username'], password=settings['password'],
|
|
36
|
+
secure_channel=settings.get('secure', False),
|
|
37
|
+
cluster_uuid=settings.get('cluster_name'),
|
|
38
|
+
ssl_cert=settings.get('ssl_cert'),
|
|
39
|
+
grpc_options=settings.get('grpc_options'), timeout=15)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class _ObservedSyncManager(ClusterManager):
|
|
43
|
+
"""Observe inherited real RPCs without replacing the transport."""
|
|
44
|
+
|
|
45
|
+
def _try_cluster_request(self, operation):
|
|
46
|
+
self.operations.append(operation)
|
|
47
|
+
try:
|
|
48
|
+
return super()._try_cluster_request(operation)
|
|
49
|
+
except grpc.RpcError as error:
|
|
50
|
+
self.errors.append(error)
|
|
51
|
+
raise
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class _ObservedAsyncManager(AsyncClusterManager):
|
|
55
|
+
"""Observe inherited real RPCs without replacing the transport."""
|
|
56
|
+
|
|
57
|
+
async def _request(self, client, operation, deadline):
|
|
58
|
+
self.operations.append(operation)
|
|
59
|
+
try:
|
|
60
|
+
return await super()._request(client, operation, deadline)
|
|
61
|
+
except grpc.RpcError as error:
|
|
62
|
+
self.errors.append(error)
|
|
63
|
+
raise
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _observed_manager(manager_type, config, *, timeout=15, debug=False):
|
|
67
|
+
options = _manager_options(config)
|
|
68
|
+
options.update(timeout=timeout, debug=debug)
|
|
69
|
+
manager = manager_type(**options)
|
|
70
|
+
manager.operations = []
|
|
71
|
+
manager.errors = []
|
|
72
|
+
return manager
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _assert_disabled(error):
|
|
76
|
+
assert error.code() == grpc.StatusCode.FAILED_PRECONDITION
|
|
77
|
+
assert 'disabled' in error.details().lower()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_sync_active_cluster_does_not_dispatch_resume(live_config):
|
|
81
|
+
if os.environ.get('E6_RESUME_ACTIVE_AUTHORIZED') != '1':
|
|
82
|
+
pytest.skip('Requires an explicitly prepared active target')
|
|
83
|
+
if not live_config.connection_kwargs.get('password'):
|
|
84
|
+
pytest.skip('This regression requires the legacy password authentication path')
|
|
85
|
+
manager = _observed_manager(_ObservedSyncManager, live_config, debug=True)
|
|
86
|
+
try:
|
|
87
|
+
assert manager.resume() is True
|
|
88
|
+
assert manager.operations == ['status']
|
|
89
|
+
assert manager.errors == []
|
|
90
|
+
finally:
|
|
91
|
+
if hasattr(manager, '_channel'):
|
|
92
|
+
manager._channel.close()
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_sync_resume_preserves_original_terminal_error(rejected_resume_config):
|
|
96
|
+
manager = _observed_manager(_ObservedSyncManager, rejected_resume_config)
|
|
97
|
+
try:
|
|
98
|
+
with pytest.raises(grpc.RpcError) as raised:
|
|
99
|
+
manager.resume()
|
|
100
|
+
_assert_disabled(raised.value)
|
|
101
|
+
assert raised.value is manager.errors[-1]
|
|
102
|
+
assert manager.operations == ['status', 'resume']
|
|
103
|
+
finally:
|
|
104
|
+
if hasattr(manager, '_channel'):
|
|
105
|
+
manager._channel.close()
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def test_sync_query_preserves_disabled_resume_error(rejected_resume_config):
|
|
109
|
+
options = dict(rejected_resume_config.connection_kwargs, auto_resume=True)
|
|
110
|
+
with pytest.raises(grpc.RpcError) as raised:
|
|
111
|
+
with Connection(**options) as connection:
|
|
112
|
+
with connection.cursor() as cursor:
|
|
113
|
+
cursor.execute(rejected_resume_config.read_only_sql)
|
|
114
|
+
_assert_disabled(raised.value)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def test_async_resume_preserves_terminal_reason_and_cause(rejected_resume_config):
|
|
118
|
+
async def run():
|
|
119
|
+
manager = _observed_manager(_ObservedAsyncManager, rejected_resume_config)
|
|
120
|
+
try:
|
|
121
|
+
with pytest.raises(OperationalError) as raised:
|
|
122
|
+
await manager.resume()
|
|
123
|
+
cause = raised.value.__cause__
|
|
124
|
+
_assert_disabled(cause)
|
|
125
|
+
assert cause is manager.errors[-1]
|
|
126
|
+
assert cause.details() in str(raised.value)
|
|
127
|
+
assert manager.operations == ['status', 'resume']
|
|
128
|
+
finally:
|
|
129
|
+
await manager.close()
|
|
130
|
+
asyncio.run(run())
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def test_async_rejected_resume_allows_a_later_attempt(rejected_resume_config):
|
|
134
|
+
async def run():
|
|
135
|
+
manager = _observed_manager(_ObservedAsyncManager, rejected_resume_config)
|
|
136
|
+
try:
|
|
137
|
+
for _ in range(2):
|
|
138
|
+
with pytest.raises(OperationalError) as raised:
|
|
139
|
+
await manager.resume()
|
|
140
|
+
_assert_disabled(raised.value.__cause__)
|
|
141
|
+
assert manager._mutation.pending is None
|
|
142
|
+
assert manager.operations == ['status', 'resume', 'status', 'resume']
|
|
143
|
+
finally:
|
|
144
|
+
await manager.close()
|
|
145
|
+
asyncio.run(run())
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def test_async_same_manager_recovers_after_reenable(rejected_resume_config):
|
|
149
|
+
signal_value = os.environ.get('E6_RESUME_REENABLE_SIGNAL')
|
|
150
|
+
if not signal_value:
|
|
151
|
+
pytest.skip('Requires operator re-enable coordination through a fresh signal path')
|
|
152
|
+
signal = Path(signal_value)
|
|
153
|
+
assert signal.is_absolute()
|
|
154
|
+
rejected, enabled = Path(str(signal) + '.rejected'), Path(str(signal) + '.enabled')
|
|
155
|
+
assert not rejected.exists() and not enabled.exists(), 'Use a fresh signal path'
|
|
156
|
+
|
|
157
|
+
async def run():
|
|
158
|
+
manager = _observed_manager(_ObservedAsyncManager, rejected_resume_config, timeout=300)
|
|
159
|
+
try:
|
|
160
|
+
with pytest.raises(OperationalError) as raised:
|
|
161
|
+
await manager.resume()
|
|
162
|
+
_assert_disabled(raised.value.__cause__)
|
|
163
|
+
assert manager._mutation.pending is None
|
|
164
|
+
rejected.touch(mode=0o600)
|
|
165
|
+
async with asyncio.timeout(180):
|
|
166
|
+
while not enabled.exists():
|
|
167
|
+
await asyncio.sleep(.25)
|
|
168
|
+
assert await manager.resume()
|
|
169
|
+
assert manager._mutation.pending is None
|
|
170
|
+
assert manager.operations.count('resume') == 2
|
|
171
|
+
async with AsyncConnection(**rejected_resume_config.connection_kwargs) as connection:
|
|
172
|
+
async with connection.cursor() as cursor:
|
|
173
|
+
await cursor.execute(rejected_resume_config.read_only_sql)
|
|
174
|
+
assert [list(row) for row in await cursor.fetchall()] == rejected_resume_config.expected_rows
|
|
175
|
+
finally:
|
|
176
|
+
await manager.close()
|
|
177
|
+
rejected.unlink(missing_ok=True)
|
|
178
|
+
enabled.unlink(missing_ok=True)
|
|
179
|
+
asyncio.run(run())
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/e6data_python_connector/aio.py
RENAMED
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/analyze_38_nines.py
RENAMED
|
File without changes
|
{e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/analyze_all_cases.py
RENAMED
|
File without changes
|
{e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/analyze_binary.py
RENAMED
|
File without changes
|
{e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/analyze_correct_value.py
RENAMED
|
File without changes
|
{e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/analyze_fields.py
RENAMED
|
File without changes
|
{e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/check_decimal_errors.py
RENAMED
|
File without changes
|
{e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/cleanup_test_files.py
RENAMED
|
File without changes
|
|
File without changes
|
{e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/debug_38_nines.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/integration/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/integration/support.py
RENAMED
|
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-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_comprehensive.py
RENAMED
|
File without changes
|
{e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_connection_pool.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_dialect_columns.py
RENAMED
|
File without changes
|
|
File without changes
|
{e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_improved_parsing.py
RENAMED
|
File without changes
|
{e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_known_case.py
RENAMED
|
File without changes
|
{e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_manual_analysis.py
RENAMED
|
File without changes
|
{e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_mock_server.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_oauth_auto_resume.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_specific_binary.py
RENAMED
|
File without changes
|
|
File without changes
|
{e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_strategy_logic.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/test_user_binary_value.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/validate_decimal128.py
RENAMED
|
File without changes
|
|
File without changes
|
{e6data_python_connector-3.0.1rc4 → e6data_python_connector-3.0.3rc1}/test/verify_decimal_fix.py
RENAMED
|
File without changes
|