e6data-python-connector 2.3.12rc12__tar.gz → 2.3.12rc32__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.12rc12/e6data_python_connector.egg-info → e6data_python_connector-2.3.12rc32}/PKG-INFO +89 -51
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/README.md +88 -50
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/cluster_manager.py +34 -66
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/datainputstream.py +24 -7
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/e6data_grpc.py +68 -64
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32/e6data_python_connector.egg-info}/PKG-INFO +89 -51
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/setup.py +1 -1
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/LICENSE +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/MANIFEST.in +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/__init__.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/cluster_server/__init__.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/cluster_server/cluster_pb2.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/cluster_server/cluster_pb2_grpc.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/common.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/connection_pool.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/constants.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/date_time_utils.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/dialect.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/e6x_vector/__init__.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/e6x_vector/constants.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/e6x_vector/ttypes.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/exceptions.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/server/QueryEngineService.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/server/__init__.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/server/constants.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/server/e6x_engine_pb2.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/server/e6x_engine_pb2.pyi +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/server/e6x_engine_pb2_grpc.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/server/ttypes.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/strategy.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/typeId.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector.egg-info/SOURCES.txt +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector.egg-info/dependency_links.txt +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector.egg-info/entry_points.txt +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector.egg-info/requires.txt +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector.egg-info/top_level.txt +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/gen-py/__init__.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/gen-py/e6x_vector/__init__.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/gen-py/e6x_vector/constants.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/gen-py/e6x_vector/ttypes.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/pyproject.toml +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/setup.cfg +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/__init__.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/analyze_38_nines.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/analyze_all_cases.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/analyze_binary.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/analyze_correct_value.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/analyze_fields.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/check_decimal_errors.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/cleanup_test_files.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/debug_38_nines.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/debug_binary.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/final_test.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/move_tests.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/quick_test.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_38_nines.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_all_decimal128_cases.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_cluster_manager_efficiency.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_cluster_manager_none_strategy.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_cluster_manager_strategy.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_comprehensive.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_connection_pool.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_connection_pool_e2e.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_current_implementation.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_decimal128_binary_parsing.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_decimal128_parsing.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_fix.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_improved_parsing.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_known_case.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_manual_analysis.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_mock_server.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_multiprocessing_fix.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_new_implementation.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_pool_concurrency_simple.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_pool_threading_multiprocessing.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_simple_connection_pool.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_specific_binary.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_strategy.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_strategy_logic.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_strategy_persistence_fix.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_strategy_sharing_fix.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_user_binary_value.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/tests.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/tests_grpc.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/validate_decimal128.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/validate_implementation.py +0 -0
- {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/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.12rc32
|
|
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
|
|
|
@@ -122,10 +122,11 @@ The `Connection` class supports the following parameters:
|
|
|
122
122
|
| `catalog` | str | No | None | Catalog name |
|
|
123
123
|
| `cluster_name` | str | No | None | Name of the cluster for cluster-specific operations |
|
|
124
124
|
| `secure` | bool | No | False | Enable SSL/TLS for secure connections |
|
|
125
|
+
| `ssl_cert` | str/bytes | No | None | Path to CA certificate (PEM) or certificate bytes for HTTPS connections |
|
|
125
126
|
| `auto_resume` | bool | No | True | Automatically resume cluster if suspended |
|
|
126
127
|
| `grpc_options` | dict | No | None | Additional gRPC configuration options |
|
|
127
128
|
| `debug` | bool | No | False | Enable debug logging for troubleshooting |
|
|
128
|
-
| `
|
|
129
|
+
| `require_fastbinary` | bool | No | True | Require fastbinary module for Thrift deserialization. Set to False to use pure Python implementation if system dependencies cannot be installed |
|
|
129
130
|
|
|
130
131
|
#### Secure Connection Example
|
|
131
132
|
|
|
@@ -143,14 +144,11 @@ conn = Connection(
|
|
|
143
144
|
)
|
|
144
145
|
```
|
|
145
146
|
|
|
146
|
-
####
|
|
147
|
+
#### Cluster-Specific Connection
|
|
147
148
|
|
|
148
|
-
When
|
|
149
|
+
When working with multiple clusters, specify the cluster name:
|
|
149
150
|
|
|
150
151
|
```python
|
|
151
|
-
from e6data_python_connector import Connection
|
|
152
|
-
|
|
153
|
-
# Option 1: Using CA certificate file path
|
|
154
152
|
conn = Connection(
|
|
155
153
|
host=host,
|
|
156
154
|
port=port,
|
|
@@ -158,40 +156,96 @@ conn = Connection(
|
|
|
158
156
|
password=password,
|
|
159
157
|
database=database,
|
|
160
158
|
cluster_name='analytics-cluster-01', # Specify cluster name
|
|
159
|
+
secure=True
|
|
160
|
+
)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
#### HTTPS Connection with HAProxy
|
|
164
|
+
|
|
165
|
+
When connecting through HAProxy with HTTPS, you can provide a custom CA certificate for secure connections. The `ssl_cert` parameter accepts either a file path to a PEM certificate or the certificate content as bytes.
|
|
166
|
+
|
|
167
|
+
**Using a CA certificate file path:**
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
conn = Connection(
|
|
171
|
+
host=host,
|
|
172
|
+
port=443,
|
|
173
|
+
username=username,
|
|
174
|
+
password=password,
|
|
175
|
+
database=database,
|
|
161
176
|
secure=True,
|
|
162
|
-
ssl_cert='/path/to/ca-cert.pem' # Path to CA certificate
|
|
177
|
+
ssl_cert='/path/to/ca-cert.pem' # Path to your CA certificate
|
|
163
178
|
)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**Reading certificate content as bytes:**
|
|
164
182
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
183
|
+
```python
|
|
184
|
+
# Read certificate file and pass as bytes
|
|
185
|
+
with open('/path/to/ca-cert.pem', 'rb') as cert_file:
|
|
186
|
+
cert_data = cert_file.read()
|
|
168
187
|
|
|
169
188
|
conn = Connection(
|
|
170
189
|
host=host,
|
|
171
|
-
port=
|
|
190
|
+
port=443,
|
|
172
191
|
username=username,
|
|
173
192
|
password=password,
|
|
174
193
|
database=database,
|
|
175
194
|
secure=True,
|
|
176
|
-
ssl_cert=
|
|
195
|
+
ssl_cert=cert_data # Certificate content as bytes
|
|
177
196
|
)
|
|
197
|
+
```
|
|
178
198
|
|
|
179
|
-
|
|
199
|
+
**Using system CA bundle for publicly signed certificates:**
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
# When ssl_cert is None, system default CA bundle is used
|
|
180
203
|
conn = Connection(
|
|
181
204
|
host=host,
|
|
182
|
-
port=
|
|
205
|
+
port=443,
|
|
183
206
|
username=username,
|
|
184
207
|
password=password,
|
|
185
208
|
database=database,
|
|
186
|
-
secure=True # Uses system CA bundle
|
|
209
|
+
secure=True # Uses system CA bundle by default
|
|
187
210
|
)
|
|
188
211
|
```
|
|
189
212
|
|
|
190
|
-
**
|
|
213
|
+
**Connection pooling with custom CA certificate:**
|
|
191
214
|
|
|
192
|
-
|
|
215
|
+
```python
|
|
216
|
+
pool = ConnectionPool(
|
|
217
|
+
min_size=2,
|
|
218
|
+
max_size=10,
|
|
219
|
+
host=host,
|
|
220
|
+
port=443,
|
|
221
|
+
username=username,
|
|
222
|
+
password=password,
|
|
223
|
+
database=database,
|
|
224
|
+
secure=True,
|
|
225
|
+
ssl_cert='/path/to/ca-cert.pem' # Custom CA certificate for pool connections
|
|
226
|
+
)
|
|
227
|
+
```
|
|
193
228
|
|
|
194
|
-
|
|
229
|
+
#### Handling Missing System Dependencies
|
|
230
|
+
|
|
231
|
+
The e6data connector uses the `fastbinary` module (from Apache Thrift) for optimal performance when deserializing data. This module requires system-level dependencies (`python3-devel` and `gcc-c++`) to be installed.
|
|
232
|
+
|
|
233
|
+
**Default Behavior (Recommended):**
|
|
234
|
+
By default, the connector requires `fastbinary` to be available. If it's not found, the connection will fail immediately with a clear error message:
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
conn = Connection(
|
|
238
|
+
host=host,
|
|
239
|
+
port=port,
|
|
240
|
+
username=username,
|
|
241
|
+
password=password,
|
|
242
|
+
database=database
|
|
243
|
+
)
|
|
244
|
+
# Raises exception if fastbinary is not available
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**Fallback to Pure Python:**
|
|
248
|
+
If you cannot install system dependencies (e.g., in restricted environments, serverless platforms, or containers without build tools), you can disable the `fastbinary` requirement. The connector will fall back to a pure Python implementation with a performance penalty:
|
|
195
249
|
|
|
196
250
|
```python
|
|
197
251
|
conn = Connection(
|
|
@@ -200,11 +254,23 @@ conn = Connection(
|
|
|
200
254
|
username=username,
|
|
201
255
|
password=password,
|
|
202
256
|
database=database,
|
|
203
|
-
|
|
204
|
-
secure=True
|
|
257
|
+
require_fastbinary=False # Allow operation without fastbinary
|
|
205
258
|
)
|
|
259
|
+
# Logs warning but continues with pure Python implementation
|
|
206
260
|
```
|
|
207
261
|
|
|
262
|
+
**When to use `require_fastbinary=False`:**
|
|
263
|
+
- Running in AWS Lambda or other serverless environments
|
|
264
|
+
- Docker containers built without compilation tools
|
|
265
|
+
- Restricted environments where system packages cannot be installed
|
|
266
|
+
- Development/testing environments where performance is not critical
|
|
267
|
+
|
|
268
|
+
**Performance Impact:**
|
|
269
|
+
- With `fastbinary`: Optimal performance for data deserialization
|
|
270
|
+
- Without `fastbinary` (pure Python): ~2-3x slower deserialization, but otherwise fully functional
|
|
271
|
+
|
|
272
|
+
**Note:** It's strongly recommended to install system dependencies when possible for best performance. The `require_fastbinary=False` option should only be used when system dependencies cannot be installed.
|
|
273
|
+
|
|
208
274
|
### Perform a Queries & Get Results
|
|
209
275
|
|
|
210
276
|
```python
|
|
@@ -551,35 +617,6 @@ pool.return_connection(conn)
|
|
|
551
617
|
pool.close_all()
|
|
552
618
|
```
|
|
553
619
|
|
|
554
|
-
##### Connection Pool with HTTPS (HAProxy)
|
|
555
|
-
|
|
556
|
-
```python
|
|
557
|
-
from e6data_python_connector import ConnectionPool
|
|
558
|
-
|
|
559
|
-
# Connection pool with HTTPS and custom CA certificate
|
|
560
|
-
pool = ConnectionPool(
|
|
561
|
-
min_size=2,
|
|
562
|
-
max_size=10,
|
|
563
|
-
host=host,
|
|
564
|
-
port=port,
|
|
565
|
-
username=username,
|
|
566
|
-
password=password,
|
|
567
|
-
database=database,
|
|
568
|
-
secure=True,
|
|
569
|
-
ssl_cert='/path/to/ca-cert.pem' # CA certificate for HTTPS
|
|
570
|
-
)
|
|
571
|
-
|
|
572
|
-
# Use the pool with context manager for automatic cleanup
|
|
573
|
-
with pool.get_connection_context() as conn:
|
|
574
|
-
cursor = conn.cursor()
|
|
575
|
-
cursor.execute("SELECT * FROM table")
|
|
576
|
-
results = cursor.fetchall()
|
|
577
|
-
print(f"Retrieved {len(results)} rows")
|
|
578
|
-
|
|
579
|
-
# Clean up pool when done
|
|
580
|
-
pool.close_all()
|
|
581
|
-
```
|
|
582
|
-
|
|
583
620
|
##### Using Context Manager (Recommended)
|
|
584
621
|
|
|
585
622
|
The context manager pattern ensures connections are automatically returned to the pool:
|
|
@@ -767,5 +804,6 @@ python your_script.py
|
|
|
767
804
|
| 456 Strategy Error | Automatic blue-green failover will handle this |
|
|
768
805
|
| Memory issues with large results | Use `fetchall_buffer()` instead of `fetchall()` |
|
|
769
806
|
| gRPC message size errors | Configure `grpc_options` with appropriate message size limits |
|
|
807
|
+
| fastbinary import error | Install system dependencies (`python3-devel`, `gcc-c++`) or set `require_fastbinary=False` |
|
|
770
808
|
|
|
771
809
|
See [TECH_DOC.md](TECH_DOC.md) for detailed technical documentation.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# e6data Python Connector
|
|
2
2
|
|
|
3
|
-

|
|
4
4
|
|
|
5
5
|
## Introduction
|
|
6
6
|
|
|
@@ -83,10 +83,11 @@ The `Connection` class supports the following parameters:
|
|
|
83
83
|
| `catalog` | str | No | None | Catalog name |
|
|
84
84
|
| `cluster_name` | str | No | None | Name of the cluster for cluster-specific operations |
|
|
85
85
|
| `secure` | bool | No | False | Enable SSL/TLS for secure connections |
|
|
86
|
+
| `ssl_cert` | str/bytes | No | None | Path to CA certificate (PEM) or certificate bytes for HTTPS connections |
|
|
86
87
|
| `auto_resume` | bool | No | True | Automatically resume cluster if suspended |
|
|
87
88
|
| `grpc_options` | dict | No | None | Additional gRPC configuration options |
|
|
88
89
|
| `debug` | bool | No | False | Enable debug logging for troubleshooting |
|
|
89
|
-
| `
|
|
90
|
+
| `require_fastbinary` | bool | No | True | Require fastbinary module for Thrift deserialization. Set to False to use pure Python implementation if system dependencies cannot be installed |
|
|
90
91
|
|
|
91
92
|
#### Secure Connection Example
|
|
92
93
|
|
|
@@ -104,14 +105,11 @@ conn = Connection(
|
|
|
104
105
|
)
|
|
105
106
|
```
|
|
106
107
|
|
|
107
|
-
####
|
|
108
|
+
#### Cluster-Specific Connection
|
|
108
109
|
|
|
109
|
-
When
|
|
110
|
+
When working with multiple clusters, specify the cluster name:
|
|
110
111
|
|
|
111
112
|
```python
|
|
112
|
-
from e6data_python_connector import Connection
|
|
113
|
-
|
|
114
|
-
# Option 1: Using CA certificate file path
|
|
115
113
|
conn = Connection(
|
|
116
114
|
host=host,
|
|
117
115
|
port=port,
|
|
@@ -119,40 +117,96 @@ conn = Connection(
|
|
|
119
117
|
password=password,
|
|
120
118
|
database=database,
|
|
121
119
|
cluster_name='analytics-cluster-01', # Specify cluster name
|
|
120
|
+
secure=True
|
|
121
|
+
)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
#### HTTPS Connection with HAProxy
|
|
125
|
+
|
|
126
|
+
When connecting through HAProxy with HTTPS, you can provide a custom CA certificate for secure connections. The `ssl_cert` parameter accepts either a file path to a PEM certificate or the certificate content as bytes.
|
|
127
|
+
|
|
128
|
+
**Using a CA certificate file path:**
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
conn = Connection(
|
|
132
|
+
host=host,
|
|
133
|
+
port=443,
|
|
134
|
+
username=username,
|
|
135
|
+
password=password,
|
|
136
|
+
database=database,
|
|
122
137
|
secure=True,
|
|
123
|
-
ssl_cert='/path/to/ca-cert.pem' # Path to CA certificate
|
|
138
|
+
ssl_cert='/path/to/ca-cert.pem' # Path to your CA certificate
|
|
124
139
|
)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Reading certificate content as bytes:**
|
|
125
143
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
144
|
+
```python
|
|
145
|
+
# Read certificate file and pass as bytes
|
|
146
|
+
with open('/path/to/ca-cert.pem', 'rb') as cert_file:
|
|
147
|
+
cert_data = cert_file.read()
|
|
129
148
|
|
|
130
149
|
conn = Connection(
|
|
131
150
|
host=host,
|
|
132
|
-
port=
|
|
151
|
+
port=443,
|
|
133
152
|
username=username,
|
|
134
153
|
password=password,
|
|
135
154
|
database=database,
|
|
136
155
|
secure=True,
|
|
137
|
-
ssl_cert=
|
|
156
|
+
ssl_cert=cert_data # Certificate content as bytes
|
|
138
157
|
)
|
|
158
|
+
```
|
|
139
159
|
|
|
140
|
-
|
|
160
|
+
**Using system CA bundle for publicly signed certificates:**
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
# When ssl_cert is None, system default CA bundle is used
|
|
141
164
|
conn = Connection(
|
|
142
165
|
host=host,
|
|
143
|
-
port=
|
|
166
|
+
port=443,
|
|
144
167
|
username=username,
|
|
145
168
|
password=password,
|
|
146
169
|
database=database,
|
|
147
|
-
secure=True # Uses system CA bundle
|
|
170
|
+
secure=True # Uses system CA bundle by default
|
|
148
171
|
)
|
|
149
172
|
```
|
|
150
173
|
|
|
151
|
-
**
|
|
174
|
+
**Connection pooling with custom CA certificate:**
|
|
152
175
|
|
|
153
|
-
|
|
176
|
+
```python
|
|
177
|
+
pool = ConnectionPool(
|
|
178
|
+
min_size=2,
|
|
179
|
+
max_size=10,
|
|
180
|
+
host=host,
|
|
181
|
+
port=443,
|
|
182
|
+
username=username,
|
|
183
|
+
password=password,
|
|
184
|
+
database=database,
|
|
185
|
+
secure=True,
|
|
186
|
+
ssl_cert='/path/to/ca-cert.pem' # Custom CA certificate for pool connections
|
|
187
|
+
)
|
|
188
|
+
```
|
|
154
189
|
|
|
155
|
-
|
|
190
|
+
#### Handling Missing System Dependencies
|
|
191
|
+
|
|
192
|
+
The e6data connector uses the `fastbinary` module (from Apache Thrift) for optimal performance when deserializing data. This module requires system-level dependencies (`python3-devel` and `gcc-c++`) to be installed.
|
|
193
|
+
|
|
194
|
+
**Default Behavior (Recommended):**
|
|
195
|
+
By default, the connector requires `fastbinary` to be available. If it's not found, the connection will fail immediately with a clear error message:
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
conn = Connection(
|
|
199
|
+
host=host,
|
|
200
|
+
port=port,
|
|
201
|
+
username=username,
|
|
202
|
+
password=password,
|
|
203
|
+
database=database
|
|
204
|
+
)
|
|
205
|
+
# Raises exception if fastbinary is not available
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**Fallback to Pure Python:**
|
|
209
|
+
If you cannot install system dependencies (e.g., in restricted environments, serverless platforms, or containers without build tools), you can disable the `fastbinary` requirement. The connector will fall back to a pure Python implementation with a performance penalty:
|
|
156
210
|
|
|
157
211
|
```python
|
|
158
212
|
conn = Connection(
|
|
@@ -161,11 +215,23 @@ conn = Connection(
|
|
|
161
215
|
username=username,
|
|
162
216
|
password=password,
|
|
163
217
|
database=database,
|
|
164
|
-
|
|
165
|
-
secure=True
|
|
218
|
+
require_fastbinary=False # Allow operation without fastbinary
|
|
166
219
|
)
|
|
220
|
+
# Logs warning but continues with pure Python implementation
|
|
167
221
|
```
|
|
168
222
|
|
|
223
|
+
**When to use `require_fastbinary=False`:**
|
|
224
|
+
- Running in AWS Lambda or other serverless environments
|
|
225
|
+
- Docker containers built without compilation tools
|
|
226
|
+
- Restricted environments where system packages cannot be installed
|
|
227
|
+
- Development/testing environments where performance is not critical
|
|
228
|
+
|
|
229
|
+
**Performance Impact:**
|
|
230
|
+
- With `fastbinary`: Optimal performance for data deserialization
|
|
231
|
+
- Without `fastbinary` (pure Python): ~2-3x slower deserialization, but otherwise fully functional
|
|
232
|
+
|
|
233
|
+
**Note:** It's strongly recommended to install system dependencies when possible for best performance. The `require_fastbinary=False` option should only be used when system dependencies cannot be installed.
|
|
234
|
+
|
|
169
235
|
### Perform a Queries & Get Results
|
|
170
236
|
|
|
171
237
|
```python
|
|
@@ -512,35 +578,6 @@ pool.return_connection(conn)
|
|
|
512
578
|
pool.close_all()
|
|
513
579
|
```
|
|
514
580
|
|
|
515
|
-
##### Connection Pool with HTTPS (HAProxy)
|
|
516
|
-
|
|
517
|
-
```python
|
|
518
|
-
from e6data_python_connector import ConnectionPool
|
|
519
|
-
|
|
520
|
-
# Connection pool with HTTPS and custom CA certificate
|
|
521
|
-
pool = ConnectionPool(
|
|
522
|
-
min_size=2,
|
|
523
|
-
max_size=10,
|
|
524
|
-
host=host,
|
|
525
|
-
port=port,
|
|
526
|
-
username=username,
|
|
527
|
-
password=password,
|
|
528
|
-
database=database,
|
|
529
|
-
secure=True,
|
|
530
|
-
ssl_cert='/path/to/ca-cert.pem' # CA certificate for HTTPS
|
|
531
|
-
)
|
|
532
|
-
|
|
533
|
-
# Use the pool with context manager for automatic cleanup
|
|
534
|
-
with pool.get_connection_context() as conn:
|
|
535
|
-
cursor = conn.cursor()
|
|
536
|
-
cursor.execute("SELECT * FROM table")
|
|
537
|
-
results = cursor.fetchall()
|
|
538
|
-
print(f"Retrieved {len(results)} rows")
|
|
539
|
-
|
|
540
|
-
# Clean up pool when done
|
|
541
|
-
pool.close_all()
|
|
542
|
-
```
|
|
543
|
-
|
|
544
581
|
##### Using Context Manager (Recommended)
|
|
545
582
|
|
|
546
583
|
The context manager pattern ensures connections are automatically returned to the pool:
|
|
@@ -728,5 +765,6 @@ python your_script.py
|
|
|
728
765
|
| 456 Strategy Error | Automatic blue-green failover will handle this |
|
|
729
766
|
| Memory issues with large results | Use `fetchall_buffer()` instead of `fetchall()` |
|
|
730
767
|
| gRPC message size errors | Configure `grpc_options` with appropriate message size limits |
|
|
768
|
+
| fastbinary import error | Install system dependencies (`python3-devel`, `gcc-c++`) or set `require_fastbinary=False` |
|
|
731
769
|
|
|
732
770
|
See [TECH_DOC.md](TECH_DOC.md) for detailed technical documentation.
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import threading
|
|
3
3
|
import time
|
|
4
|
-
from typing import Optional, Union
|
|
5
4
|
import e6data_python_connector.cluster_server.cluster_pb2 as cluster_pb2
|
|
6
5
|
import e6data_python_connector.cluster_server.cluster_pb2_grpc as cluster_pb2_grpc
|
|
7
6
|
import grpc
|
|
@@ -141,7 +140,7 @@ class ClusterManager:
|
|
|
141
140
|
"""
|
|
142
141
|
|
|
143
142
|
def __init__(self, host: str, port: int, user: str, password: str, secure_channel: bool = False, timeout=60 * 5,
|
|
144
|
-
cluster_uuid=None, grpc_options=None, debug=False, ssl_cert
|
|
143
|
+
cluster_uuid=None, grpc_options=None, debug=False, ssl_cert=None):
|
|
145
144
|
"""
|
|
146
145
|
Initializes a new instance of the ClusterManager class.
|
|
147
146
|
|
|
@@ -156,12 +155,9 @@ class ClusterManager:
|
|
|
156
155
|
defaults to 5 minutes.
|
|
157
156
|
cluster_uuid (str, optional): The unique identifier for the target cluster;
|
|
158
157
|
defaults to None.
|
|
159
|
-
grpc_options (dict, optional): Additional gRPC configuration options;
|
|
160
|
-
defaults to None.
|
|
161
158
|
debug (bool, optional): Enable debug logging; defaults to False.
|
|
162
|
-
ssl_cert (str or bytes, optional): Path to CA certificate file (PEM format)
|
|
163
|
-
|
|
164
|
-
If not provided and secure_channel=True, system default CA bundle will be used.
|
|
159
|
+
ssl_cert (str or bytes, optional): Path to CA certificate file (PEM format) or
|
|
160
|
+
certificate content as bytes for secure connections; defaults to None.
|
|
165
161
|
"""
|
|
166
162
|
|
|
167
163
|
self._host = host
|
|
@@ -170,66 +166,51 @@ class ClusterManager:
|
|
|
170
166
|
self._password = password
|
|
171
167
|
self._timeout = time.time() + timeout
|
|
172
168
|
self._secure_channel = secure_channel
|
|
173
|
-
self._ssl_cert = ssl_cert
|
|
174
169
|
self.cluster_uuid = cluster_uuid
|
|
175
170
|
self._grpc_options = grpc_options
|
|
176
171
|
if grpc_options is None:
|
|
177
172
|
self._grpc_options = dict()
|
|
178
173
|
self._debug = debug
|
|
174
|
+
self._ssl_cert = ssl_cert
|
|
179
175
|
|
|
180
176
|
def _get_ssl_credentials(self):
|
|
181
177
|
"""
|
|
182
|
-
|
|
178
|
+
Get SSL credentials for secure gRPC channel.
|
|
183
179
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
180
|
+
Handles three scenarios:
|
|
181
|
+
1. ssl_cert is a string (file path): Read the PEM certificate from the file
|
|
182
|
+
2. ssl_cert is bytes: Use the certificate content directly
|
|
183
|
+
3. ssl_cert is None: Use system default CA bundle
|
|
188
184
|
|
|
189
185
|
Returns:
|
|
190
|
-
grpc.ChannelCredentials: SSL credentials for secure channel
|
|
186
|
+
grpc.ChannelCredentials: SSL credentials for secure channel
|
|
191
187
|
|
|
192
188
|
Raises:
|
|
193
|
-
FileNotFoundError: If ssl_cert is a file path
|
|
194
|
-
|
|
189
|
+
FileNotFoundError: If ssl_cert is a file path but the file doesn't exist
|
|
190
|
+
IOError: If ssl_cert file cannot be read
|
|
195
191
|
"""
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
f"Failed to read CA certificate from {self._ssl_cert}: {str(e)}"
|
|
215
|
-
)
|
|
216
|
-
elif isinstance(self._ssl_cert, bytes):
|
|
217
|
-
# Certificate content provided as bytes
|
|
218
|
-
root_certificates = self._ssl_cert
|
|
219
|
-
if self._debug:
|
|
220
|
-
logger.info("ClusterManager: Using CA certificate provided as bytes")
|
|
221
|
-
else:
|
|
222
|
-
raise ValueError(
|
|
223
|
-
f"ssl_cert must be either a file path (str) or certificate content (bytes), "
|
|
224
|
-
f"got {type(self._ssl_cert)}"
|
|
225
|
-
)
|
|
192
|
+
if self._ssl_cert is None:
|
|
193
|
+
# Use system default CA bundle
|
|
194
|
+
return grpc.ssl_channel_credentials()
|
|
195
|
+
elif isinstance(self._ssl_cert, str):
|
|
196
|
+
# ssl_cert is a file path - read the certificate from file
|
|
197
|
+
try:
|
|
198
|
+
with open(self._ssl_cert, 'rb') as cert_file:
|
|
199
|
+
root_ca_cert = cert_file.read()
|
|
200
|
+
return grpc.ssl_channel_credentials(root_certificates=root_ca_cert)
|
|
201
|
+
except FileNotFoundError:
|
|
202
|
+
logger.error(f"SSL certificate file not found: {self._ssl_cert}")
|
|
203
|
+
raise
|
|
204
|
+
except IOError as e:
|
|
205
|
+
logger.error(f"Failed to read SSL certificate file {self._ssl_cert}: {e}")
|
|
206
|
+
raise
|
|
207
|
+
elif isinstance(self._ssl_cert, bytes):
|
|
208
|
+
# ssl_cert is certificate content as bytes
|
|
209
|
+
return grpc.ssl_channel_credentials(root_certificates=self._ssl_cert)
|
|
226
210
|
else:
|
|
227
|
-
#
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
# Create and return SSL credentials with certificate verification enabled
|
|
232
|
-
return grpc.ssl_channel_credentials(root_certificates=root_certificates)
|
|
211
|
+
# Invalid type - log warning and use system default
|
|
212
|
+
logger.warning(f"Invalid ssl_cert type: {type(self._ssl_cert)}. Using system default CA bundle.")
|
|
213
|
+
return grpc.ssl_channel_credentials()
|
|
233
214
|
|
|
234
215
|
@property
|
|
235
216
|
def _get_connection(self):
|
|
@@ -242,29 +223,16 @@ class ClusterManager:
|
|
|
242
223
|
"""
|
|
243
224
|
|
|
244
225
|
if self._secure_channel:
|
|
245
|
-
# Get SSL credentials (handles CA cert loading and validation)
|
|
246
|
-
credentials = self._get_ssl_credentials()
|
|
247
|
-
|
|
248
226
|
self._channel = grpc.secure_channel(
|
|
249
227
|
target='{}:{}'.format(self._host, self._port),
|
|
250
228
|
options=self._grpc_options,
|
|
251
|
-
credentials=
|
|
229
|
+
credentials=self._get_ssl_credentials()
|
|
252
230
|
)
|
|
253
|
-
|
|
254
|
-
if self._debug:
|
|
255
|
-
logger.info(
|
|
256
|
-
f"ClusterManager: Created secure gRPC channel to {self._host}:{self._port} "
|
|
257
|
-
f"with SSL/TLS certificate verification enabled"
|
|
258
|
-
)
|
|
259
231
|
else:
|
|
260
232
|
self._channel = grpc.insecure_channel(
|
|
261
233
|
target='{}:{}'.format(self._host, self._port),
|
|
262
234
|
options=self._grpc_options
|
|
263
235
|
)
|
|
264
|
-
|
|
265
|
-
if self._debug:
|
|
266
|
-
logger.info(f"ClusterManager: Created insecure gRPC channel to {self._host}:{self._port}")
|
|
267
|
-
|
|
268
236
|
return cluster_pb2_grpc.ClusterServiceStub(self._channel)
|
|
269
237
|
|
|
270
238
|
def _try_cluster_request(self, request_type, payload=None):
|
|
@@ -12,20 +12,27 @@ from e6data_python_connector.e6x_vector.ttypes import Chunk, Vector, VectorType
|
|
|
12
12
|
from e6data_python_connector.constants import ZONE
|
|
13
13
|
from e6data_python_connector.date_time_utils import floor_div, floor_mod, timezone_from_offset
|
|
14
14
|
|
|
15
|
+
# Try to import fastbinary - it's optional but provides better performance
|
|
16
|
+
_fastbinary_available = False
|
|
15
17
|
try:
|
|
16
18
|
from thrift.protocol import fastbinary
|
|
19
|
+
_fastbinary_available = True
|
|
17
20
|
except ImportError:
|
|
18
|
-
|
|
19
|
-
"""
|
|
20
|
-
Failed to import fastbinary.
|
|
21
|
-
Did you install system dependencies?
|
|
22
|
-
Please verify https://github.com/e6x-labs/e6data-python-connector#dependencies
|
|
23
|
-
"""
|
|
24
|
-
)
|
|
21
|
+
pass # Will check require_fastbinary flag at connection creation time
|
|
25
22
|
|
|
26
23
|
_logger = logging.getLogger(__name__)
|
|
27
24
|
|
|
28
25
|
|
|
26
|
+
def is_fastbinary_available():
|
|
27
|
+
"""
|
|
28
|
+
Check if fastbinary module is available.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
bool: True if fastbinary is available, False otherwise
|
|
32
|
+
"""
|
|
33
|
+
return _fastbinary_available
|
|
34
|
+
|
|
35
|
+
|
|
29
36
|
def _binary_to_decimal128(binary_data, scale=None):
|
|
30
37
|
"""
|
|
31
38
|
Convert binary data to Decimal128.
|
|
@@ -504,6 +511,16 @@ def read_values_from_array(query_columns_description: list, dis: DataInputStream
|
|
|
504
511
|
|
|
505
512
|
|
|
506
513
|
def read_rows_from_chunk(query_columns_description: list, buffer):
|
|
514
|
+
"""
|
|
515
|
+
Read rows from a Thrift-encoded chunk buffer.
|
|
516
|
+
|
|
517
|
+
Args:
|
|
518
|
+
query_columns_description: List of column descriptions
|
|
519
|
+
buffer: Thrift-encoded binary buffer
|
|
520
|
+
|
|
521
|
+
Returns:
|
|
522
|
+
List of rows
|
|
523
|
+
"""
|
|
507
524
|
# Create a transport and protocol instance for deserialization
|
|
508
525
|
transport = TTransport.TMemoryBuffer(buffer)
|
|
509
526
|
protocol = TBinaryProtocol.TBinaryProtocolAccelerated(transport)
|