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.
Files changed (87) hide show
  1. {e6data_python_connector-2.3.12rc12/e6data_python_connector.egg-info → e6data_python_connector-2.3.12rc32}/PKG-INFO +89 -51
  2. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/README.md +88 -50
  3. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/cluster_manager.py +34 -66
  4. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/datainputstream.py +24 -7
  5. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/e6data_grpc.py +68 -64
  6. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32/e6data_python_connector.egg-info}/PKG-INFO +89 -51
  7. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/setup.py +1 -1
  8. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/LICENSE +0 -0
  9. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/MANIFEST.in +0 -0
  10. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/__init__.py +0 -0
  11. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/cluster_server/__init__.py +0 -0
  12. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/cluster_server/cluster_pb2.py +0 -0
  13. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/cluster_server/cluster_pb2_grpc.py +0 -0
  14. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/common.py +0 -0
  15. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/connection_pool.py +0 -0
  16. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/constants.py +0 -0
  17. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/date_time_utils.py +0 -0
  18. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/dialect.py +0 -0
  19. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/e6x_vector/__init__.py +0 -0
  20. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/e6x_vector/constants.py +0 -0
  21. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/e6x_vector/ttypes.py +0 -0
  22. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/exceptions.py +0 -0
  23. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/server/QueryEngineService.py +0 -0
  24. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/server/__init__.py +0 -0
  25. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/server/constants.py +0 -0
  26. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/server/e6x_engine_pb2.py +0 -0
  27. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/server/e6x_engine_pb2.pyi +0 -0
  28. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/server/e6x_engine_pb2_grpc.py +0 -0
  29. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/server/ttypes.py +0 -0
  30. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/strategy.py +0 -0
  31. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector/typeId.py +0 -0
  32. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector.egg-info/SOURCES.txt +0 -0
  33. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector.egg-info/dependency_links.txt +0 -0
  34. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector.egg-info/entry_points.txt +0 -0
  35. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector.egg-info/requires.txt +0 -0
  36. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/e6data_python_connector.egg-info/top_level.txt +0 -0
  37. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/gen-py/__init__.py +0 -0
  38. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/gen-py/e6x_vector/__init__.py +0 -0
  39. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/gen-py/e6x_vector/constants.py +0 -0
  40. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/gen-py/e6x_vector/ttypes.py +0 -0
  41. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/pyproject.toml +0 -0
  42. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/setup.cfg +0 -0
  43. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/__init__.py +0 -0
  44. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/analyze_38_nines.py +0 -0
  45. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/analyze_all_cases.py +0 -0
  46. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/analyze_binary.py +0 -0
  47. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/analyze_correct_value.py +0 -0
  48. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/analyze_fields.py +0 -0
  49. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/check_decimal_errors.py +0 -0
  50. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/cleanup_test_files.py +0 -0
  51. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/debug_38_nines.py +0 -0
  52. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/debug_binary.py +0 -0
  53. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/final_test.py +0 -0
  54. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/move_tests.py +0 -0
  55. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/quick_test.py +0 -0
  56. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_38_nines.py +0 -0
  57. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_all_decimal128_cases.py +0 -0
  58. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_cluster_manager_efficiency.py +0 -0
  59. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_cluster_manager_none_strategy.py +0 -0
  60. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_cluster_manager_strategy.py +0 -0
  61. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_comprehensive.py +0 -0
  62. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_connection_pool.py +0 -0
  63. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_connection_pool_e2e.py +0 -0
  64. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_current_implementation.py +0 -0
  65. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_decimal128_binary_parsing.py +0 -0
  66. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_decimal128_parsing.py +0 -0
  67. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_fix.py +0 -0
  68. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_improved_parsing.py +0 -0
  69. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_known_case.py +0 -0
  70. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_manual_analysis.py +0 -0
  71. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_mock_server.py +0 -0
  72. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_multiprocessing_fix.py +0 -0
  73. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_new_implementation.py +0 -0
  74. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_pool_concurrency_simple.py +0 -0
  75. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_pool_threading_multiprocessing.py +0 -0
  76. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_simple_connection_pool.py +0 -0
  77. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_specific_binary.py +0 -0
  78. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_strategy.py +0 -0
  79. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_strategy_logic.py +0 -0
  80. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_strategy_persistence_fix.py +0 -0
  81. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_strategy_sharing_fix.py +0 -0
  82. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/test_user_binary_value.py +0 -0
  83. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/tests.py +0 -0
  84. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/tests_grpc.py +0 -0
  85. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/validate_decimal128.py +0 -0
  86. {e6data_python_connector-2.3.12rc12 → e6data_python_connector-2.3.12rc32}/test/validate_implementation.py +0 -0
  87. {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.12rc12
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
- ![version](https://img.shields.io/badge/version-2.3.12rc12-blue.svg)
42
+ ![version](https://img.shields.io/badge/version-2.3.12rc32-blue.svg)
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
- | `ssl_cert` | str/bytes | No | None | Path to CA certificate (PEM) or certificate bytes for HTTPS connections |
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
- #### HTTPS Connection with HAProxy
147
+ #### Cluster-Specific Connection
147
148
 
148
- When connecting through HAProxy with HTTPS/TLS and custom CA certificates:
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
- # Option 2: Reading certificate content as bytes
166
- with open('/path/to/ca-cert.pem', 'rb') as f:
167
- ca_cert = f.read()
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=port,
190
+ port=443,
172
191
  username=username,
173
192
  password=password,
174
193
  database=database,
175
194
  secure=True,
176
- ssl_cert=ca_cert # Certificate as bytes
195
+ ssl_cert=cert_data # Certificate content as bytes
177
196
  )
197
+ ```
178
198
 
179
- # Option 3: System CA bundle (for publicly signed certificates)
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=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
- **Note:** Certificate verification is always enabled for security. For self-signed certificates, provide the CA certificate via the `ssl_cert` parameter.
213
+ **Connection pooling with custom CA certificate:**
191
214
 
192
- #### Cluster-Specific Connection
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
- When working with multiple clusters, specify the cluster name:
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
- cluster_name='analytics-cluster-01', # Specify cluster name
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
- ![version](https://img.shields.io/badge/version-2.3.12rc12-blue.svg)
3
+ ![version](https://img.shields.io/badge/version-2.3.12rc32-blue.svg)
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
- | `ssl_cert` | str/bytes | No | None | Path to CA certificate (PEM) or certificate bytes for HTTPS connections |
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
- #### HTTPS Connection with HAProxy
108
+ #### Cluster-Specific Connection
108
109
 
109
- When connecting through HAProxy with HTTPS/TLS and custom CA certificates:
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
- # Option 2: Reading certificate content as bytes
127
- with open('/path/to/ca-cert.pem', 'rb') as f:
128
- ca_cert = f.read()
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=port,
151
+ port=443,
133
152
  username=username,
134
153
  password=password,
135
154
  database=database,
136
155
  secure=True,
137
- ssl_cert=ca_cert # Certificate as bytes
156
+ ssl_cert=cert_data # Certificate content as bytes
138
157
  )
158
+ ```
139
159
 
140
- # Option 3: System CA bundle (for publicly signed certificates)
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=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
- **Note:** Certificate verification is always enabled for security. For self-signed certificates, provide the CA certificate via the `ssl_cert` parameter.
174
+ **Connection pooling with custom CA certificate:**
152
175
 
153
- #### Cluster-Specific Connection
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
- When working with multiple clusters, specify the cluster name:
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
- cluster_name='analytics-cluster-01', # Specify cluster name
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: Optional[Union[str, bytes]] = None):
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
- or certificate content as bytes. Used when secure_channel=True.
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
- Creates SSL/TLS credentials for secure gRPC connections.
178
+ Get SSL credentials for secure gRPC channel.
183
179
 
184
- This method handles loading CA certificates from various sources:
185
- - File path (str): Reads the certificate from the specified file
186
- - Bytes: Uses the certificate content directly
187
- - None: Uses system default CA bundle
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 creation
186
+ grpc.ChannelCredentials: SSL credentials for secure channel
191
187
 
192
188
  Raises:
193
- FileNotFoundError: If ssl_cert is a file path and the file doesn't exist
194
- ValueError: If ssl_cert format is invalid or file cannot be read
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
- root_certificates = None
197
-
198
- if self._ssl_cert:
199
- # Custom CA certificate provided
200
- if isinstance(self._ssl_cert, str):
201
- # It's a file path - read the certificate
202
- try:
203
- with open(self._ssl_cert, 'rb') as f:
204
- root_certificates = f.read()
205
- if self._debug:
206
- logger.info(f"ClusterManager: Loaded CA certificate from file: {self._ssl_cert}")
207
- except FileNotFoundError:
208
- raise FileNotFoundError(
209
- f"CA certificate file not found: {self._ssl_cert}. "
210
- f"Please ensure the file exists and the path is correct."
211
- )
212
- except Exception as e:
213
- raise ValueError(
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
- # No custom CA certificate - use system default CA bundle
228
- if self._debug:
229
- logger.info("ClusterManager: Using system default CA bundle for SSL/TLS")
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=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
- raise Exception(
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)