e6data-python-connector 3.0.1rc3__py3-none-any.whl → 3.0.2__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.
- e6data_python_connector/async_cluster_manager.py +9 -4
- e6data_python_connector/async_connection.py +30 -8
- e6data_python_connector/async_connection_pool.py +2 -0
- e6data_python_connector/async_cursor.py +3 -2
- e6data_python_connector/cluster_manager.py +4 -3
- e6data_python_connector/datainputstream.py +11 -0
- e6data_python_connector/e6data_grpc.py +18 -13
- e6data_python_connector/e6x_vector/ttypes.py +3 -0
- e6data_python_connector/result_decode.py +155 -65
- {e6data_python_connector-3.0.1rc3.dist-info → e6data_python_connector-3.0.2.dist-info}/METADATA +19 -6
- {e6data_python_connector-3.0.1rc3.dist-info → e6data_python_connector-3.0.2.dist-info}/RECORD +16 -16
- test/test_oauth_auto_resume.py +7 -2
- {e6data_python_connector-3.0.1rc3.dist-info → e6data_python_connector-3.0.2.dist-info}/WHEEL +0 -0
- {e6data_python_connector-3.0.1rc3.dist-info → e6data_python_connector-3.0.2.dist-info}/entry_points.txt +0 -0
- {e6data_python_connector-3.0.1rc3.dist-info → e6data_python_connector-3.0.2.dist-info}/licenses/LICENSE +0 -0
- {e6data_python_connector-3.0.1rc3.dist-info → e6data_python_connector-3.0.2.dist-info}/top_level.txt +0 -0
|
@@ -123,10 +123,15 @@ class AsyncClusterManager:
|
|
|
123
123
|
options = dict(grpc_options or {})
|
|
124
124
|
if any(key.removeprefix('grpc.') in ('ssl_target_name_override', 'default_authority') for key in options):
|
|
125
125
|
raise ValueError('TLS authority overrides are unsupported.')
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
126
|
+
for name in ('max_receive_message_length', 'max_send_message_length'):
|
|
127
|
+
limits = [options.pop(key) for key in (name, 'grpc.' + name) if key in options]
|
|
128
|
+
for limit in limits:
|
|
129
|
+
if (isinstance(limit, bool) or not isinstance(limit, int)
|
|
130
|
+
or (limit != -1 and limit <= 0)):
|
|
131
|
+
raise ValueError(name + ' must be -1 (unlimited) or a positive integer.')
|
|
132
|
+
if len(limits) == 2 and limits[0] != limits[1]:
|
|
133
|
+
raise ValueError('Conflicting ' + name + ' options.')
|
|
134
|
+
options['grpc.' + name] = limits[0] if limits else -1
|
|
130
135
|
self._host, self._port = host, port
|
|
131
136
|
self._user, self._password = user, password
|
|
132
137
|
self._secure_channel, self._ssl_cert = secure_channel, ssl_cert
|
|
@@ -14,6 +14,9 @@ from .oauth_common import validate_positive_timeout, validate_token_endpoint
|
|
|
14
14
|
from .server import e6x_engine_pb2 as pb, e6x_engine_pb2_grpc as bindings
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
_UNSET_RECEIVE_MESSAGE_BYTES = object()
|
|
18
|
+
|
|
19
|
+
|
|
17
20
|
@dataclass(frozen=True)
|
|
18
21
|
class QueryRoute:
|
|
19
22
|
target: str
|
|
@@ -50,7 +53,7 @@ class AsyncConnection:
|
|
|
50
53
|
token_url=None, oauth_scope=None, access_token=None,
|
|
51
54
|
client_auth_method='basic', *, operation_timeout=600.0,
|
|
52
55
|
oauth_timeout=10.0, cleanup_timeout=10.0, auto_resume_timeout=300.0,
|
|
53
|
-
max_receive_message_bytes=
|
|
56
|
+
max_receive_message_bytes=_UNSET_RECEIVE_MESSAGE_BYTES,
|
|
54
57
|
enable_result_batch_v2=False):
|
|
55
58
|
from .result_decode_worker import is_decode_worker
|
|
56
59
|
if is_decode_worker():
|
|
@@ -98,14 +101,31 @@ class AsyncConnection:
|
|
|
98
101
|
for name, value in [('operation_timeout', operation_timeout), ('oauth_timeout', oauth_timeout),
|
|
99
102
|
('cleanup_timeout', cleanup_timeout), ('auto_resume_timeout', auto_resume_timeout)]:
|
|
100
103
|
settings[name] = validate_positive_timeout(value, name)
|
|
101
|
-
if isinstance(max_receive_message_bytes, bool) or not isinstance(max_receive_message_bytes, int) or max_receive_message_bytes <= 0:
|
|
102
|
-
raise ValueError('max_receive_message_bytes must be a positive integer.')
|
|
103
104
|
options = dict(grpc_options or {})
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
105
|
+
receive_names = ('max_receive_message_length', 'grpc.max_receive_message_length')
|
|
106
|
+
receive_values = [options.pop(name) for name in receive_names if name in options]
|
|
107
|
+
for value in receive_values:
|
|
108
|
+
if (isinstance(value, bool) or not isinstance(value, int)
|
|
109
|
+
or (value != -1 and value <= 0)):
|
|
110
|
+
raise ValueError('max_receive_message_length must be -1 (unlimited) or a positive integer.')
|
|
111
|
+
if len(receive_values) == 2 and receive_values[0] != receive_values[1]:
|
|
112
|
+
raise ValueError('Conflicting max_receive_message_length options.')
|
|
113
|
+
if max_receive_message_bytes is _UNSET_RECEIVE_MESSAGE_BYTES:
|
|
114
|
+
max_receive_message_bytes = receive_values[0] if receive_values else -1
|
|
115
|
+
if (isinstance(max_receive_message_bytes, bool) or not isinstance(max_receive_message_bytes, int)
|
|
116
|
+
or (max_receive_message_bytes != -1 and max_receive_message_bytes <= 0)):
|
|
117
|
+
raise ValueError('max_receive_message_bytes must be -1 (unlimited) or a positive integer.')
|
|
118
|
+
if receive_values and receive_values[0] != max_receive_message_bytes:
|
|
119
|
+
raise ValueError('Set the receive limit using max_receive_message_bytes; supplied options must agree.')
|
|
120
|
+
send_names = ('max_send_message_length', 'grpc.max_send_message_length')
|
|
121
|
+
send_values = [options.pop(name) for name in send_names if name in options]
|
|
122
|
+
for value in send_values:
|
|
123
|
+
if (isinstance(value, bool) or not isinstance(value, int)
|
|
124
|
+
or (value != -1 and value <= 0)):
|
|
125
|
+
raise ValueError('max_send_message_length must be -1 (unlimited) or a positive integer.')
|
|
126
|
+
if len(send_values) == 2 and send_values[0] != send_values[1]:
|
|
127
|
+
raise ValueError('Conflicting max_send_message_length options.')
|
|
128
|
+
options['grpc.max_send_message_length'] = send_values[0] if send_values else -1
|
|
109
129
|
# Authority overrides can disable meaningful server identity validation.
|
|
110
130
|
if any(name.removeprefix('grpc.') in ('ssl_target_name_override', 'default_authority') for name in options):
|
|
111
131
|
raise ValueError('TLS authority overrides are unsupported.')
|
|
@@ -385,6 +405,8 @@ class AsyncConnection:
|
|
|
385
405
|
|
|
386
406
|
self._cluster_manager = AsyncClusterManager(
|
|
387
407
|
self.host, self.port, user=self.username or '', password=self.password or '',
|
|
408
|
+
grpc_options={**self.grpc_options,
|
|
409
|
+
'grpc.max_receive_message_length': self.max_receive_message_bytes},
|
|
388
410
|
secure_channel=self.secure, cluster_uuid=self.cluster_name,
|
|
389
411
|
ssl_cert=self.ssl_cert, metadata_provider=metadata_provider if self._oauth_enabled else None,
|
|
390
412
|
initial_strategy=self.strategy, auto_resume_timeout=self.auto_resume_timeout,
|
|
@@ -189,6 +189,8 @@ class AsyncConnectionPool:
|
|
|
189
189
|
from .async_cluster_manager import AsyncClusterManager
|
|
190
190
|
self._cluster_manager = AsyncClusterManager(
|
|
191
191
|
config.host, config.port, user=config.username or '', password=config.password or '',
|
|
192
|
+
grpc_options={**config.grpc_options,
|
|
193
|
+
'grpc.max_receive_message_length': config.max_receive_message_bytes},
|
|
192
194
|
secure_channel=config.secure, ssl_cert=config.ssl_cert,
|
|
193
195
|
cluster_uuid=config.cluster_name, initial_strategy='blue',
|
|
194
196
|
metadata_provider=self._resume_metadata if config._oauth_enabled else None,
|
|
@@ -129,7 +129,7 @@ class AsyncCursor:
|
|
|
129
129
|
if self._state == 'CLOSED':
|
|
130
130
|
raise ProgrammingError('Cursor is closed.')
|
|
131
131
|
if fetch and self._failure is not None:
|
|
132
|
-
raise self._failure
|
|
132
|
+
raise IncompleteResultError(self._failure.reason, query_id=self._failure.query_id)
|
|
133
133
|
if fetch and self._state in ('EMPTY', 'SUBMISSION_UNKNOWN'):
|
|
134
134
|
raise ProgrammingError('No complete query is available for fetching.')
|
|
135
135
|
|
|
@@ -172,12 +172,13 @@ class AsyncCursor:
|
|
|
172
172
|
self._retire_prefetch()
|
|
173
173
|
self._cancel_decode()
|
|
174
174
|
if self._failure is None:
|
|
175
|
+
# Never raise this template: its traceback would retain decoded inputs.
|
|
175
176
|
self._failure = IncompleteResultError(reason, query_id=self.query_id)
|
|
176
177
|
if self._state != 'CLOSED':
|
|
177
178
|
self._state = 'RESULT_FAILED'
|
|
178
179
|
self._rows.clear()
|
|
179
180
|
self._result_batches.clear()
|
|
180
|
-
return self._failure
|
|
181
|
+
return IncompleteResultError(self._failure.reason, query_id=self._failure.query_id)
|
|
181
182
|
|
|
182
183
|
def _accept_rows(self, rows):
|
|
183
184
|
self._rows.extend(rows)
|
|
@@ -205,9 +205,10 @@ class ClusterManager:
|
|
|
205
205
|
self._timeout = time.time() + timeout
|
|
206
206
|
self._secure_channel = secure_channel
|
|
207
207
|
self.cluster_uuid = cluster_uuid
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
208
|
+
options = dict(grpc_options or ())
|
|
209
|
+
options.setdefault('grpc.max_receive_message_length', -1)
|
|
210
|
+
options.setdefault('grpc.max_send_message_length', -1)
|
|
211
|
+
self._grpc_options = list(options.items())
|
|
211
212
|
self._debug = debug
|
|
212
213
|
self._ssl_cert = ssl_cert
|
|
213
214
|
self._metadata_provider = metadata_provider
|
|
@@ -478,6 +478,8 @@ def read_values_from_array(query_columns_description: list, dis: DataInputStream
|
|
|
478
478
|
try:
|
|
479
479
|
if dtype == "LONG":
|
|
480
480
|
value_array.append(dis.read_long())
|
|
481
|
+
elif dtype == "INTERVAL_DAY_SECOND":
|
|
482
|
+
value_array.append(timedelta(microseconds=dis.read_long()))
|
|
481
483
|
elif dtype == "DATE":
|
|
482
484
|
value_array.append(format_iso_date_from_epoch_micros(dis.read_long()))
|
|
483
485
|
elif dtype == "DATETIME":
|
|
@@ -582,6 +584,15 @@ def get_column_from_chunk(vector: Vector, strict=False) -> list:
|
|
|
582
584
|
continue
|
|
583
585
|
value_array.append(vector.data.int64Data.data[
|
|
584
586
|
row] if not vector.isConstantVector else vector.data.numericConstantData.data)
|
|
587
|
+
elif d_type == VectorType.INTERVAL_DAY_SECOND:
|
|
588
|
+
for row in range(vector.size):
|
|
589
|
+
if get_null(vector, row):
|
|
590
|
+
value_array.append(None)
|
|
591
|
+
continue
|
|
592
|
+
microseconds = (vector.data.int64Data.data[row]
|
|
593
|
+
if not vector.isConstantVector
|
|
594
|
+
else vector.data.numericConstantData.data)
|
|
595
|
+
value_array.append(timedelta(microseconds=microseconds))
|
|
585
596
|
elif d_type == VectorType.DATE:
|
|
586
597
|
# Use the JDBC-parity formatter so years > 9999 emit "+YYYYY-MM-DD"
|
|
587
598
|
# 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
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
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:
|
|
532
|
+
limit = normalized.get('max_receive_message_length', -1)
|
|
533
|
+
if isinstance(limit, bool) or not isinstance(limit, int) or (limit != -1 and limit <= 0):
|
|
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))
|
|
@@ -696,7 +697,7 @@ class Connection(object):
|
|
|
696
697
|
default_options = {
|
|
697
698
|
"keepalive_timeout_ms": 900000, # Time in milliseconds to keep the connection alive.
|
|
698
699
|
"max_receive_message_length": -1, # Maximum size of received messages.
|
|
699
|
-
"max_send_message_length":
|
|
700
|
+
"max_send_message_length": -1, # Unlimited sent message size.
|
|
700
701
|
"grpc_prepare_timeout": self.grpc_prepare_timeout, # Timeout for prepare statement API call.
|
|
701
702
|
"keepalive_time_ms": 30000, # Time in milliseconds between keep-alive pings.
|
|
702
703
|
"keepalive_permit_without_calls": 1, # Allow keep-alives with no active RPCs.
|
|
@@ -1032,6 +1033,7 @@ class Connection(object):
|
|
|
1032
1033
|
cluster_uuid=self.cluster_name,
|
|
1033
1034
|
timeout=self.grpc_auto_resume_timeout_seconds,
|
|
1034
1035
|
debug=self._debug,
|
|
1036
|
+
grpc_options=self._get_grpc_options,
|
|
1035
1037
|
ssl_cert=self._ssl_cert
|
|
1036
1038
|
).resume()
|
|
1037
1039
|
return status # Return boolean status directly
|
|
@@ -1709,7 +1711,9 @@ class Cursor(DBAPICursor):
|
|
|
1709
1711
|
self._result_failure = IncompleteResultError(reason, query_id=self._query_id)
|
|
1710
1712
|
self._data = None
|
|
1711
1713
|
self._result_batches.clear()
|
|
1712
|
-
|
|
1714
|
+
# Keep the cached template free of tracebacks and response frames.
|
|
1715
|
+
return IncompleteResultError(
|
|
1716
|
+
self._result_failure.reason, query_id=self._result_failure.query_id)
|
|
1713
1717
|
|
|
1714
1718
|
def _check_result(self):
|
|
1715
1719
|
if self._result_batch_v2_enabled and (self._result_pid != os.getpid() or
|
|
@@ -1720,7 +1724,8 @@ class Cursor(DBAPICursor):
|
|
|
1720
1724
|
if self._closed:
|
|
1721
1725
|
raise ProgrammingError('Cursor is closed.')
|
|
1722
1726
|
if self._result_failure is not None:
|
|
1723
|
-
raise
|
|
1727
|
+
raise IncompleteResultError(
|
|
1728
|
+
self._result_failure.reason, query_id=self._result_failure.query_id)
|
|
1724
1729
|
|
|
1725
1730
|
@property
|
|
1726
1731
|
def query_id(self):
|
|
@@ -35,6 +35,7 @@ class VectorType(object):
|
|
|
35
35
|
NULL = 14
|
|
36
36
|
TIMESTAMP_TZ = 15
|
|
37
37
|
DECIMAL128 = 16
|
|
38
|
+
INTERVAL_DAY_SECOND = 17
|
|
38
39
|
|
|
39
40
|
_VALUES_TO_NAMES = {
|
|
40
41
|
0: "BINARY",
|
|
@@ -54,6 +55,7 @@ class VectorType(object):
|
|
|
54
55
|
14: "NULL",
|
|
55
56
|
15: "TIMESTAMP_TZ",
|
|
56
57
|
16: "DECIMAL128",
|
|
58
|
+
17: "INTERVAL_DAY_SECOND",
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
_NAMES_TO_VALUES = {
|
|
@@ -74,6 +76,7 @@ class VectorType(object):
|
|
|
74
76
|
"NULL": 14,
|
|
75
77
|
"TIMESTAMP_TZ": 15,
|
|
76
78
|
"DECIMAL128": 16,
|
|
79
|
+
"INTERVAL_DAY_SECOND": 17,
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
|
|
@@ -32,6 +32,32 @@ class ResultDecodeError(ValueError):
|
|
|
32
32
|
"""A worker failed before a complete envelope could be published."""
|
|
33
33
|
|
|
34
34
|
|
|
35
|
+
class _WorkerTransportError(ResultDecodeError):
|
|
36
|
+
"""Only worker exit and pipe I/O failures permit a local sequential retry."""
|
|
37
|
+
|
|
38
|
+
def __init__(self, stage, index, pid, exitcode=None, exception_type=None):
|
|
39
|
+
self.stage = stage
|
|
40
|
+
self.index = index
|
|
41
|
+
self.pid = pid
|
|
42
|
+
self.exitcode = exitcode
|
|
43
|
+
self.exception_type = exception_type
|
|
44
|
+
super().__init__(
|
|
45
|
+
'Decode worker unavailable: stage=%s worker=%s pid=%s exitcode=%s error_type=%s.'
|
|
46
|
+
% (stage, index, pid, exitcode, exception_type))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _detached_error(error):
|
|
50
|
+
"""Keep safe failure metadata without retaining result-bearing tracebacks."""
|
|
51
|
+
if isinstance(error, _WorkerTransportError):
|
|
52
|
+
return _WorkerTransportError(error.stage, error.index, error.pid,
|
|
53
|
+
error.exitcode, error.exception_type)
|
|
54
|
+
if isinstance(error, TimeoutError):
|
|
55
|
+
return TimeoutError('Result decoding deadline exceeded.')
|
|
56
|
+
if isinstance(error, ResultDecodeError):
|
|
57
|
+
return ResultDecodeError(str(error))
|
|
58
|
+
return ResultDecodeError('Result decoder failed (%s).' % type(error).__name__)
|
|
59
|
+
|
|
60
|
+
|
|
35
61
|
class _DecodeOwner:
|
|
36
62
|
"""One lease's cancellation latch for one envelope, with identity semantics."""
|
|
37
63
|
|
|
@@ -81,6 +107,7 @@ class _Slot:
|
|
|
81
107
|
self.error = None
|
|
82
108
|
|
|
83
109
|
def io(self):
|
|
110
|
+
stage = 'handshake'
|
|
84
111
|
try:
|
|
85
112
|
ready = self.channel.recv()
|
|
86
113
|
if not isinstance(ready, tuple) or len(ready) != 2 or ready[0] != 'ready':
|
|
@@ -94,16 +121,22 @@ class _Slot:
|
|
|
94
121
|
# so it cannot wait forever on the now-empty queue.
|
|
95
122
|
while self.runtime.state in ('starting', 'ready'):
|
|
96
123
|
job = self.jobs.get()
|
|
124
|
+
stage = 'send'
|
|
97
125
|
self.channel.send(job)
|
|
98
126
|
if job is None:
|
|
99
127
|
return
|
|
128
|
+
stage = 'receive'
|
|
100
129
|
result = self.channel.recv()
|
|
101
130
|
with self.runtime.condition:
|
|
102
131
|
self.result = result
|
|
103
132
|
self.runtime.condition.notify_all()
|
|
104
133
|
except (Exception, SystemExit) as error:
|
|
105
134
|
with self.runtime.condition:
|
|
106
|
-
|
|
135
|
+
if isinstance(error, (EOFError, OSError)):
|
|
136
|
+
self.error = _WorkerTransportError(
|
|
137
|
+
stage, self.index, self.pid, exception_type=type(error).__name__)
|
|
138
|
+
else:
|
|
139
|
+
self.error = _detached_error(error)
|
|
107
140
|
self.runtime.condition.notify_all()
|
|
108
141
|
|
|
109
142
|
|
|
@@ -115,6 +148,7 @@ class _Runtime:
|
|
|
115
148
|
self.state = 'starting'
|
|
116
149
|
self.error = None
|
|
117
150
|
self._active = None
|
|
151
|
+
self._active_cancelled = False
|
|
118
152
|
self._sequence = 0
|
|
119
153
|
self.cleanup_thread = None
|
|
120
154
|
self.stopped = threading.Event()
|
|
@@ -125,17 +159,23 @@ class _Runtime:
|
|
|
125
159
|
def pids(self):
|
|
126
160
|
return tuple(slot.pid for slot in self.slots if slot.pid is not None)
|
|
127
161
|
|
|
128
|
-
def
|
|
162
|
+
def _worker_failure(self):
|
|
163
|
+
# A protocol failure remains fatal even if another worker exits.
|
|
164
|
+
for slot in self.slots:
|
|
165
|
+
if slot.error is not None and not isinstance(slot.error, _WorkerTransportError):
|
|
166
|
+
return _detached_error(slot.error)
|
|
129
167
|
for slot in self.slots:
|
|
130
|
-
if slot.error is not None:
|
|
131
|
-
return True
|
|
132
168
|
if slot.process is not None and slot.pid is not None:
|
|
133
169
|
try:
|
|
134
170
|
if wait([slot.process.sentinel], timeout=0):
|
|
135
|
-
return
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
171
|
+
return _WorkerTransportError('exit', slot.index, slot.pid,
|
|
172
|
+
exitcode=slot.process.exitcode)
|
|
173
|
+
except (ValueError, OSError) as error:
|
|
174
|
+
return ResultDecodeError('Decode worker health check failed (%s).'
|
|
175
|
+
% type(error).__name__)
|
|
176
|
+
if slot.error is not None:
|
|
177
|
+
return _detached_error(slot.error)
|
|
178
|
+
return None
|
|
139
179
|
|
|
140
180
|
def start(self, deadline):
|
|
141
181
|
try:
|
|
@@ -144,7 +184,7 @@ class _Runtime:
|
|
|
144
184
|
_remaining(deadline)
|
|
145
185
|
with self.condition:
|
|
146
186
|
if self.error is not None:
|
|
147
|
-
raise self.error
|
|
187
|
+
raise _detached_error(self.error)
|
|
148
188
|
if self.state != 'starting':
|
|
149
189
|
raise ResultDecodeError('Decoder closed during worker startup.')
|
|
150
190
|
slot = _Slot(self, index)
|
|
@@ -164,20 +204,21 @@ class _Runtime:
|
|
|
164
204
|
with self.condition:
|
|
165
205
|
while not all(slot.ready for slot in self.slots):
|
|
166
206
|
if self.error is not None:
|
|
167
|
-
raise self.error
|
|
168
|
-
|
|
169
|
-
|
|
207
|
+
raise _detached_error(self.error)
|
|
208
|
+
failure = self._worker_failure()
|
|
209
|
+
if failure is not None:
|
|
210
|
+
raise failure
|
|
170
211
|
self.condition.wait(min(.02, _remaining(deadline)))
|
|
171
212
|
_remaining(deadline)
|
|
172
213
|
if self.error is not None:
|
|
173
|
-
raise self.error
|
|
214
|
+
raise _detached_error(self.error)
|
|
174
215
|
if self.state != 'starting':
|
|
175
216
|
raise ResultDecodeError('Decoder closed during worker startup.')
|
|
176
217
|
self.state = 'ready'
|
|
177
218
|
self.startup_done.set()
|
|
178
219
|
self.condition.notify_all()
|
|
179
220
|
except BaseException as error:
|
|
180
|
-
self.startup_error = error
|
|
221
|
+
self.startup_error = _detached_error(error)
|
|
181
222
|
self.startup_done.set()
|
|
182
223
|
self.quarantine(error, deadline)
|
|
183
224
|
self.stopped.wait(max(0, deadline - time.monotonic()))
|
|
@@ -188,13 +229,13 @@ class _Runtime:
|
|
|
188
229
|
while not self.startup_done.is_set():
|
|
189
230
|
self.condition.wait(min(.02, _remaining(deadline)))
|
|
190
231
|
if self.startup_error is not None:
|
|
191
|
-
raise self.startup_error
|
|
232
|
+
raise _detached_error(self.startup_error)
|
|
192
233
|
# A runtime that failed after startup is usable only sequentially.
|
|
193
234
|
|
|
194
235
|
def quarantine(self, error, deadline=None):
|
|
195
236
|
with self.condition:
|
|
196
237
|
if self.error is None:
|
|
197
|
-
self.error = error
|
|
238
|
+
self.error = _detached_error(error)
|
|
198
239
|
self.state = 'failed'
|
|
199
240
|
self.condition.notify_all()
|
|
200
241
|
self._request_cleanup(deadline if deadline is not None else time.monotonic() + .5)
|
|
@@ -244,6 +285,13 @@ class _Runtime:
|
|
|
244
285
|
slot.thread.join()
|
|
245
286
|
if slot.process is not None:
|
|
246
287
|
slot.process.close()
|
|
288
|
+
# No I/O thread can add another result or retain a queued job now.
|
|
289
|
+
slot.result = None
|
|
290
|
+
while True:
|
|
291
|
+
try:
|
|
292
|
+
slot.jobs.get_nowait()
|
|
293
|
+
except queue.Empty:
|
|
294
|
+
break
|
|
247
295
|
with self.condition:
|
|
248
296
|
self.state = 'closed'
|
|
249
297
|
self.stopped.set()
|
|
@@ -258,6 +306,7 @@ class _Runtime:
|
|
|
258
306
|
owner.cancelled = True
|
|
259
307
|
active = self._active
|
|
260
308
|
if active is not None and active[0] is lease and (owner is None or active[1] is owner):
|
|
309
|
+
self._active_cancelled = True
|
|
261
310
|
self.quarantine(ResultDecodeError('Result decoding was cancelled.'))
|
|
262
311
|
|
|
263
312
|
def release(self, lease, deadline, wait=True):
|
|
@@ -276,20 +325,26 @@ class _Runtime:
|
|
|
276
325
|
_logger.debug('result_decode cleanup=%s workers=%d',
|
|
277
326
|
'stopped' if self.stopped.is_set() else 'pending', len(self.pids))
|
|
278
327
|
|
|
328
|
+
def _check_decode(self, lease, owner, deadline):
|
|
329
|
+
_remaining(deadline)
|
|
330
|
+
if lease._closed:
|
|
331
|
+
raise ValueError('Decoder lease is closed.')
|
|
332
|
+
if isinstance(owner, _DecodeOwner):
|
|
333
|
+
if owner.lease is not lease:
|
|
334
|
+
raise ValueError('Decode owner belongs to another lease.')
|
|
335
|
+
if owner.cancelled:
|
|
336
|
+
raise ResultDecodeError('Result decoding was cancelled.')
|
|
337
|
+
if (self._active is not None and self._active[0] is lease
|
|
338
|
+
and self._active[1] is owner and self._active_cancelled):
|
|
339
|
+
raise ResultDecodeError('Result decoding was cancelled.')
|
|
340
|
+
|
|
279
341
|
def decode(self, lease, columns, payloads, deadline, owner):
|
|
280
342
|
started = time.monotonic()
|
|
281
343
|
mode, fallback = 'sequential', 'single_chunk'
|
|
282
344
|
admitted = False
|
|
283
345
|
try:
|
|
284
346
|
with self.condition:
|
|
285
|
-
|
|
286
|
-
if lease._closed:
|
|
287
|
-
raise ValueError('Decoder lease is closed.')
|
|
288
|
-
if isinstance(owner, _DecodeOwner):
|
|
289
|
-
if owner.lease is not lease:
|
|
290
|
-
raise ValueError('Decode owner belongs to another lease.')
|
|
291
|
-
if owner.cancelled:
|
|
292
|
-
raise ResultDecodeError('Result decoding was cancelled.')
|
|
347
|
+
self._check_decode(lease, owner, deadline)
|
|
293
348
|
if len(payloads) > 1:
|
|
294
349
|
if self.state != 'ready':
|
|
295
350
|
fallback = 'unavailable'
|
|
@@ -299,29 +354,52 @@ class _Runtime:
|
|
|
299
354
|
fallback = 'decimal_context'
|
|
300
355
|
else:
|
|
301
356
|
self._active = (lease, owner)
|
|
357
|
+
self._active_cancelled = False
|
|
302
358
|
self._sequence += 1
|
|
303
359
|
token = self._sequence
|
|
304
360
|
admitted = True
|
|
305
361
|
mode, fallback = 'parallel', 'none'
|
|
306
362
|
if not admitted:
|
|
307
363
|
chunks = decode_result_batches(columns, payloads)
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
364
|
+
else:
|
|
365
|
+
original_flags = dict(decimal.getcontext().flags)
|
|
366
|
+
try:
|
|
367
|
+
chunks = self._parallel(columns, payloads, deadline, token, lease, owner)
|
|
368
|
+
except _WorkerTransportError as error:
|
|
369
|
+
# Retire both processes and I/O threads before decoding the
|
|
370
|
+
# complete already-downloaded envelope in the caller.
|
|
371
|
+
self.quarantine(error, deadline)
|
|
372
|
+
_logger.warning('result_decode recovery=started %s', error)
|
|
373
|
+
error.__traceback__ = None
|
|
374
|
+
while not self.stopped.is_set():
|
|
375
|
+
with self.condition:
|
|
376
|
+
self._check_decode(lease, owner, deadline)
|
|
377
|
+
self.stopped.wait(min(.02, _remaining(deadline)))
|
|
378
|
+
with self.condition:
|
|
379
|
+
self._check_decode(lease, owner, deadline)
|
|
380
|
+
decimal.getcontext().flags = original_flags
|
|
381
|
+
mode, fallback = 'sequential', 'worker_transport'
|
|
382
|
+
chunks = decode_result_batches(columns, payloads)
|
|
383
|
+
with self.condition:
|
|
384
|
+
self._check_decode(lease, owner, deadline)
|
|
385
|
+
_logger.debug('result_decode recovery=complete chunks=%d', len(payloads))
|
|
386
|
+
except BaseException as error:
|
|
387
|
+
self.quarantine(error, deadline)
|
|
388
|
+
raise
|
|
389
|
+
with self.condition:
|
|
390
|
+
self._check_decode(lease, owner, deadline)
|
|
391
|
+
return chunks
|
|
315
392
|
finally:
|
|
316
393
|
if admitted:
|
|
317
394
|
with self.condition:
|
|
318
395
|
self._active = None
|
|
396
|
+
self._active_cancelled = False
|
|
319
397
|
self.condition.notify_all()
|
|
320
398
|
_logger.debug('result_decode mode=%s wall_ms=%.3f chunks=%d workers=%d fallback=%s cleanup=%s',
|
|
321
399
|
mode, (time.monotonic() - started) * 1000, len(payloads),
|
|
322
400
|
2 if admitted else 0, fallback, self.state)
|
|
323
401
|
|
|
324
|
-
def _parallel(self, columns, payloads, deadline, token):
|
|
402
|
+
def _parallel(self, columns, payloads, deadline, token, lease, owner):
|
|
325
403
|
# The existing vector decoder uses only the number of columns. Plain
|
|
326
404
|
# positions preserve that contract without serializing FieldInfo objects.
|
|
327
405
|
metadata = tuple(range(len(columns)))
|
|
@@ -330,39 +408,51 @@ class _Runtime:
|
|
|
330
408
|
output = [None] * len(payloads)
|
|
331
409
|
pending = {}
|
|
332
410
|
next_index = 0
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
index
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
411
|
+
result = None
|
|
412
|
+
try:
|
|
413
|
+
with self.condition:
|
|
414
|
+
while pending or next_index < len(payloads):
|
|
415
|
+
self._check_decode(lease, owner, deadline)
|
|
416
|
+
if self.error is not None:
|
|
417
|
+
raise _detached_error(self.error)
|
|
418
|
+
# Validate available replies before considering recovery, so
|
|
419
|
+
# a worker/protocol error is never hidden by a second exit.
|
|
420
|
+
for slot in self.slots:
|
|
421
|
+
if slot.index in pending and slot.result is not None:
|
|
422
|
+
result, slot.result = slot.result, None
|
|
423
|
+
index = pending.pop(slot.index)
|
|
424
|
+
if (not isinstance(result, tuple) or len(result) != 5 or
|
|
425
|
+
result[1] != token or result[2] != index):
|
|
426
|
+
raise ResultDecodeError('Decode worker returned an invalid job result.')
|
|
427
|
+
flags = result[4]
|
|
428
|
+
if not isinstance(flags, tuple) or any(name not in decimal_signals for name in flags):
|
|
429
|
+
raise ResultDecodeError('Decode worker returned invalid decimal flags.')
|
|
430
|
+
for name in flags:
|
|
431
|
+
context.flags[decimal_signals[name]] = True
|
|
432
|
+
if result[0] == 'error':
|
|
433
|
+
raise ResultDecodeError('Result chunk decoding failed in worker (%s).' % result[3])
|
|
434
|
+
if result[0] != 'result':
|
|
435
|
+
raise ResultDecodeError('Decode worker returned an invalid result kind.')
|
|
436
|
+
output[index] = result[3]
|
|
437
|
+
failure = self._worker_failure()
|
|
438
|
+
if failure is not None:
|
|
439
|
+
raise failure
|
|
440
|
+
for slot in self.slots:
|
|
441
|
+
if slot.index not in pending and next_index < len(payloads):
|
|
442
|
+
index = next_index
|
|
443
|
+
next_index += 1
|
|
444
|
+
pending[slot.index] = index
|
|
445
|
+
slot.jobs.put_nowait((token, index, metadata, payloads[index]))
|
|
446
|
+
if pending:
|
|
447
|
+
self.condition.wait(min(.02, _remaining(deadline)))
|
|
448
|
+
self._check_decode(lease, owner, deadline)
|
|
449
|
+
return [rows for rows in output if rows]
|
|
450
|
+
finally:
|
|
451
|
+
# Fatal errors may keep their traceback for the caller. Do not let
|
|
452
|
+
# that traceback also retain partial decoded output or pending jobs.
|
|
453
|
+
output.clear()
|
|
454
|
+
pending.clear()
|
|
455
|
+
result = None
|
|
366
456
|
|
|
367
457
|
|
|
368
458
|
class DecoderLease:
|
{e6data_python_connector-3.0.1rc3.dist-info → e6data_python_connector-3.0.2.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: e6data-python-connector
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.2
|
|
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.
|
|
@@ -489,11 +489,15 @@ workers use the explicit `spawn` method; the application's global multiprocessin
|
|
|
489
489
|
start method is unchanged. An unguarded main script can run its other import
|
|
490
490
|
side effects, so keep all application startup inside the guard.
|
|
491
491
|
|
|
492
|
-
Both APIs default to
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
492
|
+
Both APIs default to unlimited gRPC send and receive message sizes (`-1`).
|
|
493
|
+
Explicit positive byte limits are still honored. For sync, set
|
|
494
|
+
`max_receive_message_length` and `max_send_message_length` in `grpc_options`.
|
|
495
|
+
For async, set the receive limit with `max_receive_message_bytes` or
|
|
496
|
+
`max_receive_message_length` in `grpc_options`; if both are supplied, they must
|
|
497
|
+
agree. Set `max_send_message_length` in `grpc_options` for the send limit.
|
|
498
|
+
The receive limit applies to each message after transport decompression, not the
|
|
499
|
+
total query result. Unlimited receiving does not limit client memory use;
|
|
500
|
+
continuous downloading can retain serialized batches while decoding catches up.
|
|
497
501
|
|
|
498
502
|
These examples use your existing `connection_options`, `sql`, and `consume`:
|
|
499
503
|
|
|
@@ -567,6 +571,15 @@ downloading is faster than decoding or application reads. Workers add Python
|
|
|
567
571
|
objects and process copies; the receive limit is not a limit on total memory.
|
|
568
572
|
`fetchall()` still retains the full result.
|
|
569
573
|
|
|
574
|
+
If a decoder worker exits or its local pipe breaks, the connector stops the
|
|
575
|
+
failed workers and decodes that complete serialized envelope once in the parent
|
|
576
|
+
process. It uses the same deadline and makes no extra result RPC. Continuous
|
|
577
|
+
downloading stays active and uncapped during this recovery. Later envelopes use
|
|
578
|
+
sequential decoding until the decoder runtime is replaced through its normal
|
|
579
|
+
lifecycle. Cancellation, expired deadlines, invalid worker replies, and errors
|
|
580
|
+
reported while decoding data are still failures. Their original cause is kept
|
|
581
|
+
on the first error delivered to the caller.
|
|
582
|
+
|
|
570
583
|
Only a V2 `UNIMPLEMENTED` response switches that query to V1 and disables its
|
|
571
584
|
optimizations. Other RPC failures reach the caller as the original
|
|
572
585
|
`grpc.RpcError` or `grpc.aio.AioRpcError`. A failed pending RPC is raised when
|
{e6data_python_connector-3.0.1rc3.dist-info → e6data_python_connector-3.0.2.dist-info}/RECORD
RENAMED
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
e6data_python_connector/__init__.py,sha256=gA6M_1qwKwJmGfhSa_-s694bkIcnxtJtdY1shIxL0Oc,188
|
|
2
2
|
e6data_python_connector/aio.py,sha256=aqBWuQ1G7zVJfWAJDiEb3stTTq0w58ACmZlPFGR8Ym8,930
|
|
3
|
-
e6data_python_connector/async_cluster_manager.py,sha256=
|
|
4
|
-
e6data_python_connector/async_connection.py,sha256=
|
|
5
|
-
e6data_python_connector/async_connection_pool.py,sha256=
|
|
6
|
-
e6data_python_connector/async_cursor.py,sha256=
|
|
3
|
+
e6data_python_connector/async_cluster_manager.py,sha256=iNJNVNpISyxgQik5d8wOUys-pZ3r6RAq99NPYemrIOk,12891
|
|
4
|
+
e6data_python_connector/async_connection.py,sha256=xK5gEjeU8htu-IM21KDtRP3S3mKAPs0OxF3m_IPa-1Q,31106
|
|
5
|
+
e6data_python_connector/async_connection_pool.py,sha256=E_51RwZ3YMQCANKmWCw0_GNkZehgeF2NFVJ5IaK-aAY,19083
|
|
6
|
+
e6data_python_connector/async_cursor.py,sha256=RO0RW1lkM22p2RPdVxI1JkczXHvwNNSTpe8cvYiqy4w,41987
|
|
7
7
|
e6data_python_connector/async_dialect.py,sha256=4rC8G2VRl7Wd91if5YSEgOdUI6Y02M7voowMF5gMT0Y,11004
|
|
8
8
|
e6data_python_connector/async_oauth.py,sha256=0AHXJYKZ7m1FIZEt98R93ARLBbQoAOUSMVJn3fdXGQ0,11405
|
|
9
9
|
e6data_python_connector/async_work.py,sha256=DJxCoacbiEgzZDcEUnpEQfXU32z05TOn_2stV0Xd2VY,3814
|
|
10
|
-
e6data_python_connector/cluster_manager.py,sha256=
|
|
10
|
+
e6data_python_connector/cluster_manager.py,sha256=k7LbzKaX1thKiELm057X7JuOAtglPLlmoG6KhaYCxvQ,28835
|
|
11
11
|
e6data_python_connector/common.py,sha256=dPUbTuX0nKYjDfhVxCLevv6RCxi35DXZVgFbdM5S9vI,11748
|
|
12
12
|
e6data_python_connector/connection_pool.py,sha256=GNweiefokl36ve8riwBlPaBbw5aApy0_Pjm0iqZdqbY,17918
|
|
13
13
|
e6data_python_connector/constants.py,sha256=AwACS9iUaYj84oRRHj9Waep9LGbNHxcE07BABV7s45Y,1406
|
|
14
|
-
e6data_python_connector/datainputstream.py,sha256=
|
|
14
|
+
e6data_python_connector/datainputstream.py,sha256=3bEXVV9LtKivpdV8Yy8FHS3GIcp4lR8HFyLWXKBIVnA,30364
|
|
15
15
|
e6data_python_connector/date_time_utils.py,sha256=yjUWIx2QiGtE6Tp8hGIzh7nwF4wtJr9teB8FQ99B4TA,21305
|
|
16
16
|
e6data_python_connector/dialect.py,sha256=vylcvLT2vkTZDVYA3MSHvaxqf7S-A2F9sGlLT4v9vxE,13239
|
|
17
|
-
e6data_python_connector/e6data_grpc.py,sha256=
|
|
17
|
+
e6data_python_connector/e6data_grpc.py,sha256=ecc4C7grrs7T38WNUnwEfRFEw9q2brVwqHu_Y97qeOw,120912
|
|
18
18
|
e6data_python_connector/exceptions.py,sha256=jU4C0B6zs_mBgzZ6H85KRs6Qe7QuQaWFhQjN9Xfwa1A,1504
|
|
19
19
|
e6data_python_connector/oauth.py,sha256=7bMe0Wj4EpWSwYER1ny7oPtx5jEvD6Us9N8ACJB1Heo,8168
|
|
20
20
|
e6data_python_connector/oauth_cache.py,sha256=jdPqp2XV0EEGHgzcBRXhTHntk1rf4VXWZZVbjhlyuk8,8403
|
|
21
21
|
e6data_python_connector/oauth_common.py,sha256=m-7sMmgGQDanMqmUv21ASB0rVTrFs-vi7PzVRp-61qM,2935
|
|
22
22
|
e6data_python_connector/result_batch.py,sha256=idiV2MZ1exZRlb3HSD6PlIRFQYjPmajXHm_WXenvlrI,1642
|
|
23
|
-
e6data_python_connector/result_decode.py,sha256=
|
|
23
|
+
e6data_python_connector/result_decode.py,sha256=5yFh4cdkwuXlsAhByMvVCji76JSy8KrUCi54HouEThM,24293
|
|
24
24
|
e6data_python_connector/result_decode_worker.py,sha256=ih5ErdeKFU4vy9mqqH_bwbrP8Q9JwtKNSNGwn5Ya6d0,1978
|
|
25
25
|
e6data_python_connector/result_prefetch.py,sha256=22oXTyekVYc90jci0HFr8VMBlrO5JJd8FuPKTCvsCrw,14835
|
|
26
26
|
e6data_python_connector/strategy.py,sha256=whAC-C031XFirP71hXKqdiMTJRIUUEvc34tuo2W8k-0,6539
|
|
@@ -30,7 +30,7 @@ e6data_python_connector/cluster_server/cluster_pb2.py,sha256=BWxO5vG1SVkx2y-i6A6
|
|
|
30
30
|
e6data_python_connector/cluster_server/cluster_pb2_grpc.py,sha256=tfnRKs2GQGwdSH8zxT40yveN4XnD9j7aWHGUfGkPHiM,5399
|
|
31
31
|
e6data_python_connector/e6x_vector/__init__.py,sha256=YFcZTT8Cm-6Y4oTiCaqq0DT1lw2W09WqoEc5_pTAwW0,34
|
|
32
32
|
e6data_python_connector/e6x_vector/constants.py,sha256=E5Gsf7749sDKQedzdEsv-98HXUqUF8qM_THivZ34YBI,388
|
|
33
|
-
e6data_python_connector/e6x_vector/ttypes.py,sha256=
|
|
33
|
+
e6data_python_connector/e6x_vector/ttypes.py,sha256=ZMaRvOOiDMOD-UnS_G5GOmUcdc0YiH1MCuzKHE8D1R0,63768
|
|
34
34
|
e6data_python_connector/server/QueryEngineService.py,sha256=IDX2Yi9m_QFZ8L4FoiYrl8i7YL8GjTRonblKKFZsapU,198503
|
|
35
35
|
e6data_python_connector/server/__init__.py,sha256=8eK3WgN4xfJiSNm4bUrnXb5ZgjMUEjdStbUahQQrJII,56
|
|
36
36
|
e6data_python_connector/server/constants.py,sha256=QPD8cJw4a7t7JD4PTzbxj1CZKZ1lQ0TPK2Uzg72S9dE,366
|
|
@@ -38,7 +38,7 @@ e6data_python_connector/server/e6x_engine_pb2.py,sha256=gAtG-MclLOc_uSrn5vRVMNJx
|
|
|
38
38
|
e6data_python_connector/server/e6x_engine_pb2.pyi,sha256=NCDX-xF2ZH1Q5k-hg27yuow1ILWsJTCAg8YD-qTQ5kY,34815
|
|
39
39
|
e6data_python_connector/server/e6x_engine_pb2_grpc.py,sha256=stzKD9uSKRAOLdpFMD-owJvunnxrXvhvJnHRCGjmDzU,75778
|
|
40
40
|
e6data_python_connector/server/ttypes.py,sha256=uWxwSp4m6e-7SeSEckbZtSduGYmfzXWM09iy5eltBPA,21227
|
|
41
|
-
e6data_python_connector-3.0.
|
|
41
|
+
e6data_python_connector-3.0.2.dist-info/licenses/LICENSE,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
|
|
42
42
|
test/__init__.py,sha256=s574pVF7fqLitVI45JvBdqBMN8DBmzHG-1Te-QcnUU0,47
|
|
43
43
|
test/analyze_38_nines.py,sha256=99QMuCDMUoxh5LV9JgceYSZKlChhNS_2kaV56EQBv-A,3608
|
|
44
44
|
test/analyze_all_cases.py,sha256=mEeN6E_7tTl_HP4UXbQ3vREO81mqVEaxl_QPTxc6Z5Q,4079
|
|
@@ -75,7 +75,7 @@ test/test_mock_server.py,sha256=RKRIgngbt28QDje0i2ushZ4zZj_N5GeM6d4L5ExHorw,6061
|
|
|
75
75
|
test/test_multiprocessing_fix.py,sha256=Ql0mDOvtptS3Dl3wbwPLZ_N90fdK1MG9eqk4WHSMIfU,4238
|
|
76
76
|
test/test_new_implementation.py,sha256=tjmQ4xLCmButPWIoIZm_DeWei8QkwI13XmbBlr3mBwY,5518
|
|
77
77
|
test/test_oauth_authentication.py,sha256=grfkmnv_DbBTtpiBDguwpL71EtUNAMvdg5hl9k5j7z0,27199
|
|
78
|
-
test/test_oauth_auto_resume.py,sha256=
|
|
78
|
+
test/test_oauth_auto_resume.py,sha256=OacdlcF8xCtoY6KmiFMXBPSEq4-lnBogu5n3qATvmpI,22060
|
|
79
79
|
test/test_oauth_resume_integration.py,sha256=8tedzK-s7PsRqYKWRjNCJk8nvSw9QPq8PXjnObaH4Q0,11448
|
|
80
80
|
test/test_pool_concurrency_simple.py,sha256=BMzEYgqNEH7UYE4PnNpvky0S_7ejlLOkiOjVfuo-JPw,20338
|
|
81
81
|
test/test_pool_threading_multiprocessing.py,sha256=VxXW4YDlX09NfbIWTr05Knc7OvB4E58kW_1UqonUPPE,24085
|
|
@@ -111,8 +111,8 @@ test/integration/test_result_batch_v2.py,sha256=f2Wiqhfcq7wMNT4hSOVYSLXUIoYV9FZV
|
|
|
111
111
|
test/integration/test_shared_token_issuance.py,sha256=PSsjZBlLq58v1Fw8scSaRwmtNkO49LcDQfG9_jmSkPM,4761
|
|
112
112
|
test/integration/test_sync_oauth_lifecycle.py,sha256=HqQk1HBm0vyQmS1xMggXSSn0Pg5LJ7xnlxx2zpnRl3k,831
|
|
113
113
|
test/integration/token_observation.py,sha256=uDrTFJeg8UalhJYo76c-i0Xm1SkO5bq5rlT4OP72vp4,1995
|
|
114
|
-
e6data_python_connector-3.0.
|
|
115
|
-
e6data_python_connector-3.0.
|
|
116
|
-
e6data_python_connector-3.0.
|
|
117
|
-
e6data_python_connector-3.0.
|
|
118
|
-
e6data_python_connector-3.0.
|
|
114
|
+
e6data_python_connector-3.0.2.dist-info/METADATA,sha256=5EgZgPYWhjtrz7Fmn_MmmZ1VGQGjK8rLZ0rWE0Le3vk,40031
|
|
115
|
+
e6data_python_connector-3.0.2.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
116
|
+
e6data_python_connector-3.0.2.dist-info/entry_points.txt,sha256=KWgkagF_tw3w5RhD2tVXqrIHn8ibNg2e53LdPz78t1k,151
|
|
117
|
+
e6data_python_connector-3.0.2.dist-info/top_level.txt,sha256=uOWV7xMMlZxe030EIU4bGUuKJ-W78lKxX4YHA7YFclw,29
|
|
118
|
+
e6data_python_connector-3.0.2.dist-info/RECORD,,
|
test/test_oauth_auto_resume.py
CHANGED
|
@@ -349,7 +349,9 @@ def test_legacy_suspend_resume_baseline(cached, active):
|
|
|
349
349
|
connector._set_active_strategy(cached)
|
|
350
350
|
state = ['suspended']
|
|
351
351
|
seen = []
|
|
352
|
-
connection = make_connection(access_token=None, username='fixture-user', password='fixture-password'
|
|
352
|
+
connection = make_connection(access_token=None, username='fixture-user', password='fixture-password',
|
|
353
|
+
grpc_options={'max_receive_message_length': 4096,
|
|
354
|
+
'max_send_message_length': 8192})
|
|
353
355
|
def authenticate(request, metadata):
|
|
354
356
|
assert request.user == 'fixture-user'
|
|
355
357
|
assert request.password == 'fixture-password'
|
|
@@ -361,7 +363,10 @@ def test_legacy_suspend_resume_baseline(cached, active):
|
|
|
361
363
|
if headers['strategy'] != active:
|
|
362
364
|
raise rpc_error(grpc.StatusCode.UNKNOWN, 'status: 456')
|
|
363
365
|
return engine_pb.AuthenticateResponse(sessionId='legacy-session')
|
|
364
|
-
def resume(
|
|
366
|
+
def resume(manager):
|
|
367
|
+
options = dict(manager._grpc_options)
|
|
368
|
+
assert options['grpc.max_receive_message_length'] == 4096
|
|
369
|
+
assert options['grpc.max_send_message_length'] == 8192
|
|
365
370
|
state[0] = 'active'
|
|
366
371
|
return True
|
|
367
372
|
connection._client = SimpleNamespace(authenticate=authenticate)
|
{e6data_python_connector-3.0.1rc3.dist-info → e6data_python_connector-3.0.2.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{e6data_python_connector-3.0.1rc3.dist-info → e6data_python_connector-3.0.2.dist-info}/top_level.txt
RENAMED
|
File without changes
|