e6data-python-connector 2.3.12rc11__tar.gz → 2.3.12rc12__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.12rc11/e6data_python_connector.egg-info → e6data_python_connector-2.3.12rc12}/PKG-INFO +78 -2
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/README.md +77 -1
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/e6data_python_connector/cluster_manager.py +76 -2
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/e6data_python_connector/e6data_grpc.py +74 -52
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12/e6data_python_connector.egg-info}/PKG-INFO +78 -2
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/setup.py +1 -1
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/LICENSE +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/MANIFEST.in +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/e6data_python_connector/__init__.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/e6data_python_connector/cluster_server/__init__.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/e6data_python_connector/cluster_server/cluster_pb2.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/e6data_python_connector/cluster_server/cluster_pb2_grpc.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/e6data_python_connector/common.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/e6data_python_connector/connection_pool.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/e6data_python_connector/constants.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/e6data_python_connector/datainputstream.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/e6data_python_connector/date_time_utils.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/e6data_python_connector/dialect.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/e6data_python_connector/e6x_vector/__init__.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/e6data_python_connector/e6x_vector/constants.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/e6data_python_connector/e6x_vector/ttypes.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/e6data_python_connector/exceptions.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/e6data_python_connector/server/QueryEngineService.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/e6data_python_connector/server/__init__.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/e6data_python_connector/server/constants.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/e6data_python_connector/server/e6x_engine_pb2.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/e6data_python_connector/server/e6x_engine_pb2.pyi +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/e6data_python_connector/server/e6x_engine_pb2_grpc.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/e6data_python_connector/server/ttypes.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/e6data_python_connector/strategy.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/e6data_python_connector/typeId.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/e6data_python_connector.egg-info/SOURCES.txt +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/e6data_python_connector.egg-info/dependency_links.txt +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/e6data_python_connector.egg-info/entry_points.txt +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/e6data_python_connector.egg-info/requires.txt +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/e6data_python_connector.egg-info/top_level.txt +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/gen-py/__init__.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/gen-py/e6x_vector/__init__.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/gen-py/e6x_vector/constants.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/gen-py/e6x_vector/ttypes.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/pyproject.toml +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/setup.cfg +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/__init__.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/analyze_38_nines.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/analyze_all_cases.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/analyze_binary.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/analyze_correct_value.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/analyze_fields.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/check_decimal_errors.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/cleanup_test_files.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/debug_38_nines.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/debug_binary.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/final_test.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/move_tests.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/quick_test.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/test_38_nines.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/test_all_decimal128_cases.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/test_cluster_manager_efficiency.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/test_cluster_manager_none_strategy.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/test_cluster_manager_strategy.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/test_comprehensive.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/test_connection_pool.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/test_connection_pool_e2e.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/test_current_implementation.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/test_decimal128_binary_parsing.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/test_decimal128_parsing.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/test_fix.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/test_improved_parsing.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/test_known_case.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/test_manual_analysis.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/test_mock_server.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/test_multiprocessing_fix.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/test_new_implementation.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/test_pool_concurrency_simple.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/test_pool_threading_multiprocessing.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/test_simple_connection_pool.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/test_specific_binary.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/test_strategy.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/test_strategy_logic.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/test_strategy_persistence_fix.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/test_strategy_sharing_fix.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/test_user_binary_value.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/tests.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/tests_grpc.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/validate_decimal128.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/validate_implementation.py +0 -0
- {e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/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.12rc12
|
|
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
|
|
|
@@ -125,6 +125,7 @@ The `Connection` class supports the following parameters:
|
|
|
125
125
|
| `auto_resume` | bool | No | True | Automatically resume cluster if suspended |
|
|
126
126
|
| `grpc_options` | dict | No | None | Additional gRPC configuration options |
|
|
127
127
|
| `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 |
|
|
128
129
|
|
|
129
130
|
#### Secure Connection Example
|
|
130
131
|
|
|
@@ -142,6 +143,52 @@ conn = Connection(
|
|
|
142
143
|
)
|
|
143
144
|
```
|
|
144
145
|
|
|
146
|
+
#### HTTPS Connection with HAProxy
|
|
147
|
+
|
|
148
|
+
When connecting through HAProxy with HTTPS/TLS and custom CA certificates:
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
from e6data_python_connector import Connection
|
|
152
|
+
|
|
153
|
+
# Option 1: Using CA certificate file path
|
|
154
|
+
conn = Connection(
|
|
155
|
+
host=host,
|
|
156
|
+
port=port,
|
|
157
|
+
username=username,
|
|
158
|
+
password=password,
|
|
159
|
+
database=database,
|
|
160
|
+
cluster_name='analytics-cluster-01', # Specify cluster name
|
|
161
|
+
secure=True,
|
|
162
|
+
ssl_cert='/path/to/ca-cert.pem' # Path to CA certificate
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# Option 2: Reading certificate content as bytes
|
|
166
|
+
with open('/path/to/ca-cert.pem', 'rb') as f:
|
|
167
|
+
ca_cert = f.read()
|
|
168
|
+
|
|
169
|
+
conn = Connection(
|
|
170
|
+
host=host,
|
|
171
|
+
port=port,
|
|
172
|
+
username=username,
|
|
173
|
+
password=password,
|
|
174
|
+
database=database,
|
|
175
|
+
secure=True,
|
|
176
|
+
ssl_cert=ca_cert # Certificate as bytes
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
# Option 3: System CA bundle (for publicly signed certificates)
|
|
180
|
+
conn = Connection(
|
|
181
|
+
host=host,
|
|
182
|
+
port=port,
|
|
183
|
+
username=username,
|
|
184
|
+
password=password,
|
|
185
|
+
database=database,
|
|
186
|
+
secure=True # Uses system CA bundle
|
|
187
|
+
)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**Note:** Certificate verification is always enabled for security. For self-signed certificates, provide the CA certificate via the `ssl_cert` parameter.
|
|
191
|
+
|
|
145
192
|
#### Cluster-Specific Connection
|
|
146
193
|
|
|
147
194
|
When working with multiple clusters, specify the cluster name:
|
|
@@ -504,6 +551,35 @@ pool.return_connection(conn)
|
|
|
504
551
|
pool.close_all()
|
|
505
552
|
```
|
|
506
553
|
|
|
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
|
+
|
|
507
583
|
##### Using Context Manager (Recommended)
|
|
508
584
|
|
|
509
585
|
The context manager pattern ensures connections are automatically returned to the pool:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# e6data Python Connector
|
|
2
2
|
|
|
3
|
-

|
|
4
4
|
|
|
5
5
|
## Introduction
|
|
6
6
|
|
|
@@ -86,6 +86,7 @@ The `Connection` class supports the following parameters:
|
|
|
86
86
|
| `auto_resume` | bool | No | True | Automatically resume cluster if suspended |
|
|
87
87
|
| `grpc_options` | dict | No | None | Additional gRPC configuration options |
|
|
88
88
|
| `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 |
|
|
89
90
|
|
|
90
91
|
#### Secure Connection Example
|
|
91
92
|
|
|
@@ -103,6 +104,52 @@ conn = Connection(
|
|
|
103
104
|
)
|
|
104
105
|
```
|
|
105
106
|
|
|
107
|
+
#### HTTPS Connection with HAProxy
|
|
108
|
+
|
|
109
|
+
When connecting through HAProxy with HTTPS/TLS and custom CA certificates:
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
from e6data_python_connector import Connection
|
|
113
|
+
|
|
114
|
+
# Option 1: Using CA certificate file path
|
|
115
|
+
conn = Connection(
|
|
116
|
+
host=host,
|
|
117
|
+
port=port,
|
|
118
|
+
username=username,
|
|
119
|
+
password=password,
|
|
120
|
+
database=database,
|
|
121
|
+
cluster_name='analytics-cluster-01', # Specify cluster name
|
|
122
|
+
secure=True,
|
|
123
|
+
ssl_cert='/path/to/ca-cert.pem' # Path to CA certificate
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# Option 2: Reading certificate content as bytes
|
|
127
|
+
with open('/path/to/ca-cert.pem', 'rb') as f:
|
|
128
|
+
ca_cert = f.read()
|
|
129
|
+
|
|
130
|
+
conn = Connection(
|
|
131
|
+
host=host,
|
|
132
|
+
port=port,
|
|
133
|
+
username=username,
|
|
134
|
+
password=password,
|
|
135
|
+
database=database,
|
|
136
|
+
secure=True,
|
|
137
|
+
ssl_cert=ca_cert # Certificate as bytes
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# Option 3: System CA bundle (for publicly signed certificates)
|
|
141
|
+
conn = Connection(
|
|
142
|
+
host=host,
|
|
143
|
+
port=port,
|
|
144
|
+
username=username,
|
|
145
|
+
password=password,
|
|
146
|
+
database=database,
|
|
147
|
+
secure=True # Uses system CA bundle
|
|
148
|
+
)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**Note:** Certificate verification is always enabled for security. For self-signed certificates, provide the CA certificate via the `ssl_cert` parameter.
|
|
152
|
+
|
|
106
153
|
#### Cluster-Specific Connection
|
|
107
154
|
|
|
108
155
|
When working with multiple clusters, specify the cluster name:
|
|
@@ -465,6 +512,35 @@ pool.return_connection(conn)
|
|
|
465
512
|
pool.close_all()
|
|
466
513
|
```
|
|
467
514
|
|
|
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
|
+
|
|
468
544
|
##### Using Context Manager (Recommended)
|
|
469
545
|
|
|
470
546
|
The context manager pattern ensures connections are automatically returned to the pool:
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import threading
|
|
3
3
|
import time
|
|
4
|
+
from typing import Optional, Union
|
|
4
5
|
import e6data_python_connector.cluster_server.cluster_pb2 as cluster_pb2
|
|
5
6
|
import e6data_python_connector.cluster_server.cluster_pb2_grpc as cluster_pb2_grpc
|
|
6
7
|
import grpc
|
|
@@ -140,7 +141,7 @@ class ClusterManager:
|
|
|
140
141
|
"""
|
|
141
142
|
|
|
142
143
|
def __init__(self, host: str, port: int, user: str, password: str, secure_channel: bool = False, timeout=60 * 5,
|
|
143
|
-
cluster_uuid=None, grpc_options=None, debug=False):
|
|
144
|
+
cluster_uuid=None, grpc_options=None, debug=False, ssl_cert: Optional[Union[str, bytes]] = None):
|
|
144
145
|
"""
|
|
145
146
|
Initializes a new instance of the ClusterManager class.
|
|
146
147
|
|
|
@@ -155,7 +156,12 @@ class ClusterManager:
|
|
|
155
156
|
defaults to 5 minutes.
|
|
156
157
|
cluster_uuid (str, optional): The unique identifier for the target cluster;
|
|
157
158
|
defaults to None.
|
|
159
|
+
grpc_options (dict, optional): Additional gRPC configuration options;
|
|
160
|
+
defaults to None.
|
|
158
161
|
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
165
|
"""
|
|
160
166
|
|
|
161
167
|
self._host = host
|
|
@@ -164,12 +170,67 @@ class ClusterManager:
|
|
|
164
170
|
self._password = password
|
|
165
171
|
self._timeout = time.time() + timeout
|
|
166
172
|
self._secure_channel = secure_channel
|
|
173
|
+
self._ssl_cert = ssl_cert
|
|
167
174
|
self.cluster_uuid = cluster_uuid
|
|
168
175
|
self._grpc_options = grpc_options
|
|
169
176
|
if grpc_options is None:
|
|
170
177
|
self._grpc_options = dict()
|
|
171
178
|
self._debug = debug
|
|
172
179
|
|
|
180
|
+
def _get_ssl_credentials(self):
|
|
181
|
+
"""
|
|
182
|
+
Creates SSL/TLS credentials for secure gRPC connections.
|
|
183
|
+
|
|
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
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
grpc.ChannelCredentials: SSL credentials for secure channel creation
|
|
191
|
+
|
|
192
|
+
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
|
|
195
|
+
"""
|
|
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
|
+
)
|
|
226
|
+
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)
|
|
233
|
+
|
|
173
234
|
@property
|
|
174
235
|
def _get_connection(self):
|
|
175
236
|
"""
|
|
@@ -181,16 +242,29 @@ class ClusterManager:
|
|
|
181
242
|
"""
|
|
182
243
|
|
|
183
244
|
if self._secure_channel:
|
|
245
|
+
# Get SSL credentials (handles CA cert loading and validation)
|
|
246
|
+
credentials = self._get_ssl_credentials()
|
|
247
|
+
|
|
184
248
|
self._channel = grpc.secure_channel(
|
|
185
249
|
target='{}:{}'.format(self._host, self._port),
|
|
186
250
|
options=self._grpc_options,
|
|
187
|
-
credentials=
|
|
251
|
+
credentials=credentials
|
|
188
252
|
)
|
|
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
|
+
)
|
|
189
259
|
else:
|
|
190
260
|
self._channel = grpc.insecure_channel(
|
|
191
261
|
target='{}:{}'.format(self._host, self._port),
|
|
192
262
|
options=self._grpc_options
|
|
193
263
|
)
|
|
264
|
+
|
|
265
|
+
if self._debug:
|
|
266
|
+
logger.info(f"ClusterManager: Created insecure gRPC channel to {self._host}:{self._port}")
|
|
267
|
+
|
|
194
268
|
return cluster_pb2_grpc.ClusterServiceStub(self._channel)
|
|
195
269
|
|
|
196
270
|
def _try_cluster_request(self, request_type, payload=None):
|
|
@@ -16,6 +16,7 @@ import time
|
|
|
16
16
|
from decimal import Decimal
|
|
17
17
|
from io import BytesIO
|
|
18
18
|
from ssl import CERT_NONE, CERT_OPTIONAL, CERT_REQUIRED
|
|
19
|
+
from typing import Union, Optional
|
|
19
20
|
import threading
|
|
20
21
|
import multiprocessing
|
|
21
22
|
|
|
@@ -335,6 +336,7 @@ class Connection(object):
|
|
|
335
336
|
scheme: str = 'e6data',
|
|
336
337
|
grpc_options: dict = None,
|
|
337
338
|
debug: bool = False,
|
|
339
|
+
ssl_cert: Optional[Union[str, bytes]] = None,
|
|
338
340
|
):
|
|
339
341
|
"""
|
|
340
342
|
Parameters
|
|
@@ -368,6 +370,14 @@ class Connection(object):
|
|
|
368
370
|
- keepalive_time_ms: This parameter defines the time, in milliseconds, Default to 30 seconds
|
|
369
371
|
debug: bool, Optional
|
|
370
372
|
Flag to enable debug logging for blue-green deployment strategy changes
|
|
373
|
+
ssl_cert: str or bytes, Optional
|
|
374
|
+
Path to CA certificate file (PEM format) or certificate content as bytes.
|
|
375
|
+
Used when connecting via HTTPS .
|
|
376
|
+
If not provided and secure=True, system default CA bundle will be used.
|
|
377
|
+
For self-signed certificates, provide the CA certificate via this parameter.
|
|
378
|
+
Examples:
|
|
379
|
+
- File path: ssl_cert='/path/to/ca-cert.pem'
|
|
380
|
+
- Bytes: ssl_cert=open('/path/to/ca-cert.pem', 'rb').read()
|
|
371
381
|
"""
|
|
372
382
|
if not username or not password:
|
|
373
383
|
raise ValueError("username or password cannot be empty.")
|
|
@@ -382,6 +392,7 @@ class Connection(object):
|
|
|
382
392
|
self._port = port
|
|
383
393
|
|
|
384
394
|
self._secure_channel = secure
|
|
395
|
+
self._ssl_cert = ssl_cert
|
|
385
396
|
|
|
386
397
|
self.catalog_name = catalog
|
|
387
398
|
|
|
@@ -483,6 +494,60 @@ class Connection(object):
|
|
|
483
494
|
|
|
484
495
|
return self._cached_grpc_options
|
|
485
496
|
|
|
497
|
+
def _get_ssl_credentials(self):
|
|
498
|
+
"""
|
|
499
|
+
Creates SSL/TLS credentials for secure gRPC connections.
|
|
500
|
+
|
|
501
|
+
This method handles loading CA certificates from various sources:
|
|
502
|
+
- File path (str): Reads the certificate from the specified file
|
|
503
|
+
- Bytes: Uses the certificate content directly
|
|
504
|
+
- None: Uses system default CA bundle
|
|
505
|
+
|
|
506
|
+
Returns:
|
|
507
|
+
grpc.ChannelCredentials: SSL credentials for secure channel creation
|
|
508
|
+
|
|
509
|
+
Raises:
|
|
510
|
+
FileNotFoundError: If ssl_cert is a file path and the file doesn't exist
|
|
511
|
+
ValueError: If ssl_cert format is invalid or file cannot be read
|
|
512
|
+
"""
|
|
513
|
+
root_certificates = None
|
|
514
|
+
|
|
515
|
+
if self._ssl_cert:
|
|
516
|
+
# Custom CA certificate provided
|
|
517
|
+
if isinstance(self._ssl_cert, str):
|
|
518
|
+
# It's a file path - read the certificate
|
|
519
|
+
try:
|
|
520
|
+
with open(self._ssl_cert, 'rb') as f:
|
|
521
|
+
root_certificates = f.read()
|
|
522
|
+
if self._debug:
|
|
523
|
+
logger.info(f"Loaded CA certificate from file: {self._ssl_cert}")
|
|
524
|
+
except FileNotFoundError:
|
|
525
|
+
raise FileNotFoundError(
|
|
526
|
+
f"CA certificate file not found: {self._ssl_cert}. "
|
|
527
|
+
f"Please ensure the file exists and the path is correct."
|
|
528
|
+
)
|
|
529
|
+
except Exception as e:
|
|
530
|
+
raise ValueError(
|
|
531
|
+
f"Failed to read CA certificate from {self._ssl_cert}: {str(e)}"
|
|
532
|
+
)
|
|
533
|
+
elif isinstance(self._ssl_cert, bytes):
|
|
534
|
+
# Certificate content provided as bytes
|
|
535
|
+
root_certificates = self._ssl_cert
|
|
536
|
+
if self._debug:
|
|
537
|
+
logger.info("Using CA certificate provided as bytes")
|
|
538
|
+
else:
|
|
539
|
+
raise ValueError(
|
|
540
|
+
f"ssl_cert must be either a file path (str) or certificate content (bytes), "
|
|
541
|
+
f"got {type(self._ssl_cert)}"
|
|
542
|
+
)
|
|
543
|
+
else:
|
|
544
|
+
# No custom CA certificate - use system default CA bundle
|
|
545
|
+
if self._debug:
|
|
546
|
+
logger.info("Using system default CA bundle for SSL/TLS")
|
|
547
|
+
|
|
548
|
+
# Create and return SSL credentials with certificate verification enabled
|
|
549
|
+
return grpc.ssl_channel_credentials(root_certificates=root_certificates)
|
|
550
|
+
|
|
486
551
|
def _create_client(self):
|
|
487
552
|
"""
|
|
488
553
|
Creates a gRPC client for the connection.
|
|
@@ -497,13 +562,19 @@ class Connection(object):
|
|
|
497
562
|
|
|
498
563
|
Raises:
|
|
499
564
|
grpc.RpcError: If there is an error in creating the gRPC channel or client stub.
|
|
565
|
+
FileNotFoundError: If ssl_cert is a file path and the file doesn't exist.
|
|
566
|
+
ValueError: If ssl_cert format is invalid.
|
|
500
567
|
"""
|
|
501
568
|
|
|
502
569
|
if self._secure_channel:
|
|
570
|
+
# Get SSL credentials (handles CA cert loading and validation)
|
|
571
|
+
credentials = self._get_ssl_credentials()
|
|
572
|
+
|
|
573
|
+
# Create secure channel
|
|
503
574
|
self._channel = grpc.secure_channel(
|
|
504
575
|
target='{}:{}'.format(self._host, self._port),
|
|
505
576
|
options=self._get_grpc_options,
|
|
506
|
-
credentials=
|
|
577
|
+
credentials=credentials
|
|
507
578
|
)
|
|
508
579
|
else:
|
|
509
580
|
self._channel = grpc.insecure_channel(
|
|
@@ -685,7 +756,8 @@ class Connection(object):
|
|
|
685
756
|
secure_channel=self._secure_channel,
|
|
686
757
|
cluster_uuid=self.cluster_name,
|
|
687
758
|
timeout=self.grpc_auto_resume_timeout_seconds,
|
|
688
|
-
debug=self._debug
|
|
759
|
+
debug=self._debug,
|
|
760
|
+
ssl_cert=self._ssl_cert
|
|
689
761
|
).resume()
|
|
690
762
|
return status # Return boolean status directly
|
|
691
763
|
else:
|
|
@@ -853,56 +925,6 @@ class Connection(object):
|
|
|
853
925
|
)
|
|
854
926
|
return dry_run_response.dryrunValue
|
|
855
927
|
|
|
856
|
-
def get_logical_plan(self, query, catalog=None, schema=None):
|
|
857
|
-
"""
|
|
858
|
-
Gets the logical execution plan for a query without executing it.
|
|
859
|
-
This method performs syntax, parsing, and plan compilation, then returns the logical plan.
|
|
860
|
-
|
|
861
|
-
Args:
|
|
862
|
-
query (str): The SQL query to get the plan for.
|
|
863
|
-
catalog (str, optional): Catalog name. Uses connection's catalog if not provided.
|
|
864
|
-
schema (str, optional): Schema/database name. Uses connection's database if not provided.
|
|
865
|
-
|
|
866
|
-
Returns:
|
|
867
|
-
dict: A dictionary with:
|
|
868
|
-
- 'success' (bool): True if plan generation succeeded, False otherwise
|
|
869
|
-
- 'plan' (str, optional): The logical plan string if successful
|
|
870
|
-
- 'error' (str, optional): Error message if plan generation failed
|
|
871
|
-
|
|
872
|
-
Example:
|
|
873
|
-
>>> result = conn.get_logical_plan("SELECT * FROM table")
|
|
874
|
-
>>> if result['success']:
|
|
875
|
-
... print(f"Plan: {result['plan']}")
|
|
876
|
-
>>> else:
|
|
877
|
-
... print(f"Error: {result['error']}")
|
|
878
|
-
"""
|
|
879
|
-
catalog_to_use = catalog if catalog is not None else self.catalog_name
|
|
880
|
-
schema_to_use = schema if schema is not None else self.database
|
|
881
|
-
|
|
882
|
-
try:
|
|
883
|
-
dry_run_request = e6x_engine_pb2.DryRunRequestV2(
|
|
884
|
-
sessionId=self.get_session_id,
|
|
885
|
-
schema=schema_to_use,
|
|
886
|
-
catalog=catalog_to_use,
|
|
887
|
-
queryString=query
|
|
888
|
-
)
|
|
889
|
-
|
|
890
|
-
dry_run_response = self._client.dryRunV2(
|
|
891
|
-
dry_run_request,
|
|
892
|
-
metadata=_get_grpc_header(cluster=self.cluster_name, strategy=_get_active_strategy())
|
|
893
|
-
)
|
|
894
|
-
|
|
895
|
-
return {
|
|
896
|
-
'success': True,
|
|
897
|
-
'plan': dry_run_response.dryrunValue
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
except Exception as e:
|
|
901
|
-
return {
|
|
902
|
-
'success': False,
|
|
903
|
-
'error': str(e)
|
|
904
|
-
}
|
|
905
|
-
|
|
906
928
|
def get_tables(self, catalog, database):
|
|
907
929
|
"""
|
|
908
930
|
Retrieves the list of tables from the specified catalog and database.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: e6data-python-connector
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.12rc12
|
|
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
|
|
|
@@ -125,6 +125,7 @@ The `Connection` class supports the following parameters:
|
|
|
125
125
|
| `auto_resume` | bool | No | True | Automatically resume cluster if suspended |
|
|
126
126
|
| `grpc_options` | dict | No | None | Additional gRPC configuration options |
|
|
127
127
|
| `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 |
|
|
128
129
|
|
|
129
130
|
#### Secure Connection Example
|
|
130
131
|
|
|
@@ -142,6 +143,52 @@ conn = Connection(
|
|
|
142
143
|
)
|
|
143
144
|
```
|
|
144
145
|
|
|
146
|
+
#### HTTPS Connection with HAProxy
|
|
147
|
+
|
|
148
|
+
When connecting through HAProxy with HTTPS/TLS and custom CA certificates:
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
from e6data_python_connector import Connection
|
|
152
|
+
|
|
153
|
+
# Option 1: Using CA certificate file path
|
|
154
|
+
conn = Connection(
|
|
155
|
+
host=host,
|
|
156
|
+
port=port,
|
|
157
|
+
username=username,
|
|
158
|
+
password=password,
|
|
159
|
+
database=database,
|
|
160
|
+
cluster_name='analytics-cluster-01', # Specify cluster name
|
|
161
|
+
secure=True,
|
|
162
|
+
ssl_cert='/path/to/ca-cert.pem' # Path to CA certificate
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# Option 2: Reading certificate content as bytes
|
|
166
|
+
with open('/path/to/ca-cert.pem', 'rb') as f:
|
|
167
|
+
ca_cert = f.read()
|
|
168
|
+
|
|
169
|
+
conn = Connection(
|
|
170
|
+
host=host,
|
|
171
|
+
port=port,
|
|
172
|
+
username=username,
|
|
173
|
+
password=password,
|
|
174
|
+
database=database,
|
|
175
|
+
secure=True,
|
|
176
|
+
ssl_cert=ca_cert # Certificate as bytes
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
# Option 3: System CA bundle (for publicly signed certificates)
|
|
180
|
+
conn = Connection(
|
|
181
|
+
host=host,
|
|
182
|
+
port=port,
|
|
183
|
+
username=username,
|
|
184
|
+
password=password,
|
|
185
|
+
database=database,
|
|
186
|
+
secure=True # Uses system CA bundle
|
|
187
|
+
)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**Note:** Certificate verification is always enabled for security. For self-signed certificates, provide the CA certificate via the `ssl_cert` parameter.
|
|
191
|
+
|
|
145
192
|
#### Cluster-Specific Connection
|
|
146
193
|
|
|
147
194
|
When working with multiple clusters, specify the cluster name:
|
|
@@ -504,6 +551,35 @@ pool.return_connection(conn)
|
|
|
504
551
|
pool.close_all()
|
|
505
552
|
```
|
|
506
553
|
|
|
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
|
+
|
|
507
583
|
##### Using Context Manager (Recommended)
|
|
508
584
|
|
|
509
585
|
The context manager pattern ensures connections are automatically returned to the pool:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/gen-py/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/analyze_38_nines.py
RENAMED
|
File without changes
|
{e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/analyze_all_cases.py
RENAMED
|
File without changes
|
{e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/analyze_binary.py
RENAMED
|
File without changes
|
|
File without changes
|
{e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/analyze_fields.py
RENAMED
|
File without changes
|
|
File without changes
|
{e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/cleanup_test_files.py
RENAMED
|
File without changes
|
{e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/debug_38_nines.py
RENAMED
|
File without changes
|
{e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/debug_binary.py
RENAMED
|
File without changes
|
{e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/final_test.py
RENAMED
|
File without changes
|
{e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/move_tests.py
RENAMED
|
File without changes
|
{e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/quick_test.py
RENAMED
|
File without changes
|
{e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/test_38_nines.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/test_comprehensive.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/test_known_case.py
RENAMED
|
File without changes
|
|
File without changes
|
{e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/test_mock_server.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/test_strategy.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/tests_grpc.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{e6data_python_connector-2.3.12rc11 → e6data_python_connector-2.3.12rc12}/test/verify_decimal_fix.py
RENAMED
|
File without changes
|