e6data-python-connector 2.3.7rc2__tar.gz → 2.3.9__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.7rc2/e6data_python_connector.egg-info → e6data_python_connector-2.3.9}/PKG-INFO +51 -2
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/README.md +50 -1
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/e6data_python_connector/dialect.py +3 -1
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/e6data_python_connector/e6data_grpc.py +64 -37
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/e6data_python_connector/strategy.py +1 -18
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9/e6data_python_connector.egg-info}/PKG-INFO +51 -2
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/setup.py +1 -1
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/LICENSE +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/MANIFEST.in +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/e6data_python_connector/__init__.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/e6data_python_connector/cluster_manager.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/e6data_python_connector/cluster_server/__init__.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/e6data_python_connector/cluster_server/cluster_pb2.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/e6data_python_connector/cluster_server/cluster_pb2_grpc.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/e6data_python_connector/common.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/e6data_python_connector/constants.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/e6data_python_connector/datainputstream.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/e6data_python_connector/date_time_utils.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/e6data_python_connector/e6x_vector/__init__.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/e6data_python_connector/e6x_vector/constants.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/e6data_python_connector/e6x_vector/ttypes.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/e6data_python_connector/exceptions.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/e6data_python_connector/server/QueryEngineService.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/e6data_python_connector/server/__init__.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/e6data_python_connector/server/constants.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/e6data_python_connector/server/e6x_engine_pb2.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/e6data_python_connector/server/e6x_engine_pb2.pyi +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/e6data_python_connector/server/e6x_engine_pb2_grpc.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/e6data_python_connector/server/ttypes.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/e6data_python_connector/typeId.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/e6data_python_connector.egg-info/SOURCES.txt +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/e6data_python_connector.egg-info/dependency_links.txt +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/e6data_python_connector.egg-info/entry_points.txt +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/e6data_python_connector.egg-info/requires.txt +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/e6data_python_connector.egg-info/top_level.txt +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/pyproject.toml +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/setup.cfg +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/__init__.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/analyze_38_nines.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/analyze_all_cases.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/analyze_binary.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/analyze_correct_value.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/analyze_fields.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/check_decimal_errors.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/cleanup_test_files.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/debug_38_nines.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/debug_binary.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/final_test.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/move_tests.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/quick_test.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/test_38_nines.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/test_all_decimal128_cases.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/test_cluster_manager_efficiency.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/test_cluster_manager_none_strategy.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/test_cluster_manager_strategy.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/test_comprehensive.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/test_current_implementation.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/test_decimal128_binary_parsing.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/test_decimal128_parsing.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/test_fix.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/test_improved_parsing.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/test_known_case.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/test_manual_analysis.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/test_mock_server.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/test_multiprocessing_fix.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/test_new_implementation.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/test_specific_binary.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/test_strategy.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/test_strategy_logic.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/test_strategy_persistence_fix.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/test_strategy_sharing_fix.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/test_user_binary_value.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/tests.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/tests_grpc.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/validate_decimal128.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/validate_implementation.py +0 -0
- {e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/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.9
|
|
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.
|
|
@@ -38,7 +38,7 @@ Dynamic: summary
|
|
|
38
38
|
|
|
39
39
|
# e6data Python Connector
|
|
40
40
|
|
|
41
|
-

|
|
42
42
|
|
|
43
43
|
## Introduction
|
|
44
44
|
|
|
@@ -93,6 +93,55 @@ conn = Connection(
|
|
|
93
93
|
)
|
|
94
94
|
```
|
|
95
95
|
|
|
96
|
+
#### Connection Parameters
|
|
97
|
+
|
|
98
|
+
The `Connection` class supports the following parameters:
|
|
99
|
+
|
|
100
|
+
| Parameter | Type | Required | Default | Description |
|
|
101
|
+
|-----------|------|----------|---------|-------------|
|
|
102
|
+
| `host` | str | Yes | - | IP address or hostname of the e6data cluster |
|
|
103
|
+
| `port` | int | Yes | - | Port of the e6data engine (typically 80) |
|
|
104
|
+
| `username` | str | Yes | - | Your e6data Email ID |
|
|
105
|
+
| `password` | str | Yes | - | Access Token generated in the e6data console |
|
|
106
|
+
| `database` | str | No | None | Database to perform queries on |
|
|
107
|
+
| `catalog` | str | No | None | Catalog name |
|
|
108
|
+
| `cluster_name` | str | No | None | Name of the cluster for cluster-specific operations |
|
|
109
|
+
| `secure` | bool | No | False | Enable SSL/TLS for secure connections |
|
|
110
|
+
| `auto_resume` | bool | No | True | Automatically resume cluster if suspended |
|
|
111
|
+
| `grpc_options` | dict | No | None | Additional gRPC configuration options |
|
|
112
|
+
|
|
113
|
+
#### Secure Connection Example
|
|
114
|
+
|
|
115
|
+
To establish a secure connection using SSL/TLS:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
conn = Connection(
|
|
119
|
+
host=host,
|
|
120
|
+
port=443, # Typically 443 for secure connections
|
|
121
|
+
username=username,
|
|
122
|
+
password=password,
|
|
123
|
+
database=database,
|
|
124
|
+
cluster_name='production-cluster',
|
|
125
|
+
secure=True # Enable SSL/TLS
|
|
126
|
+
)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
#### Cluster-Specific Connection
|
|
130
|
+
|
|
131
|
+
When working with multiple clusters, specify the cluster name:
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
conn = Connection(
|
|
135
|
+
host=host,
|
|
136
|
+
port=port,
|
|
137
|
+
username=username,
|
|
138
|
+
password=password,
|
|
139
|
+
database=database,
|
|
140
|
+
cluster_name='analytics-cluster-01', # Specify cluster name
|
|
141
|
+
secure=True
|
|
142
|
+
)
|
|
143
|
+
```
|
|
144
|
+
|
|
96
145
|
### Perform a Queries & Get Results
|
|
97
146
|
|
|
98
147
|
```python
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# e6data Python Connector
|
|
2
2
|
|
|
3
|
-

|
|
4
4
|
|
|
5
5
|
## Introduction
|
|
6
6
|
|
|
@@ -55,6 +55,55 @@ conn = Connection(
|
|
|
55
55
|
)
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
+
#### Connection Parameters
|
|
59
|
+
|
|
60
|
+
The `Connection` class supports the following parameters:
|
|
61
|
+
|
|
62
|
+
| Parameter | Type | Required | Default | Description |
|
|
63
|
+
|-----------|------|----------|---------|-------------|
|
|
64
|
+
| `host` | str | Yes | - | IP address or hostname of the e6data cluster |
|
|
65
|
+
| `port` | int | Yes | - | Port of the e6data engine (typically 80) |
|
|
66
|
+
| `username` | str | Yes | - | Your e6data Email ID |
|
|
67
|
+
| `password` | str | Yes | - | Access Token generated in the e6data console |
|
|
68
|
+
| `database` | str | No | None | Database to perform queries on |
|
|
69
|
+
| `catalog` | str | No | None | Catalog name |
|
|
70
|
+
| `cluster_name` | str | No | None | Name of the cluster for cluster-specific operations |
|
|
71
|
+
| `secure` | bool | No | False | Enable SSL/TLS for secure connections |
|
|
72
|
+
| `auto_resume` | bool | No | True | Automatically resume cluster if suspended |
|
|
73
|
+
| `grpc_options` | dict | No | None | Additional gRPC configuration options |
|
|
74
|
+
|
|
75
|
+
#### Secure Connection Example
|
|
76
|
+
|
|
77
|
+
To establish a secure connection using SSL/TLS:
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
conn = Connection(
|
|
81
|
+
host=host,
|
|
82
|
+
port=443, # Typically 443 for secure connections
|
|
83
|
+
username=username,
|
|
84
|
+
password=password,
|
|
85
|
+
database=database,
|
|
86
|
+
cluster_name='production-cluster',
|
|
87
|
+
secure=True # Enable SSL/TLS
|
|
88
|
+
)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
#### Cluster-Specific Connection
|
|
92
|
+
|
|
93
|
+
When working with multiple clusters, specify the cluster name:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
conn = Connection(
|
|
97
|
+
host=host,
|
|
98
|
+
port=port,
|
|
99
|
+
username=username,
|
|
100
|
+
password=password,
|
|
101
|
+
database=database,
|
|
102
|
+
cluster_name='analytics-cluster-01', # Specify cluster name
|
|
103
|
+
secure=True
|
|
104
|
+
)
|
|
105
|
+
```
|
|
106
|
+
|
|
58
107
|
### Perform a Queries & Get Results
|
|
59
108
|
|
|
60
109
|
```python
|
|
@@ -229,6 +229,7 @@ class E6dataDialect(default.DefaultDialect):
|
|
|
229
229
|
self.cluster_name = url.query.get("cluster-name")
|
|
230
230
|
self.secure = url.query.get("secure") == "true"
|
|
231
231
|
self.auto_resume = url.query.get("auto-resume", "true") == "true" # default to True
|
|
232
|
+
self.debug = url.query.get("debug", "false") == "true" # default to True
|
|
232
233
|
if not self.catalog_name:
|
|
233
234
|
raise Exception('Please specify catalog in query parameter.')
|
|
234
235
|
|
|
@@ -250,7 +251,8 @@ class E6dataDialect(default.DefaultDialect):
|
|
|
250
251
|
"cluster_name": self.cluster_name,
|
|
251
252
|
'secure': self.secure,
|
|
252
253
|
'auto_resume': self.auto_resume,
|
|
253
|
-
'grpc_options': grpc_options
|
|
254
|
+
'grpc_options': grpc_options,
|
|
255
|
+
'debug': self.debug
|
|
254
256
|
}
|
|
255
257
|
return [], kwargs
|
|
256
258
|
|
|
@@ -128,26 +128,18 @@ _local_strategy_cache = {
|
|
|
128
128
|
# Strategy cache timeout in seconds (5 minutes)
|
|
129
129
|
STRATEGY_CACHE_TIMEOUT = 300
|
|
130
130
|
|
|
131
|
+
# Global set to track debug-enabled connections
|
|
132
|
+
_debug_connections = set()
|
|
133
|
+
|
|
134
|
+
def _strategy_debug_log(message):
|
|
135
|
+
"""Log strategy debug messages if any connection has debug enabled."""
|
|
136
|
+
if _debug_connections:
|
|
137
|
+
print(f"[E6DATA_STRATEGY_DEBUG] {time.strftime('%Y-%m-%d %H:%M:%S')} - {message}")
|
|
138
|
+
|
|
131
139
|
|
|
132
140
|
def _get_shared_strategy():
|
|
133
141
|
"""Get or create the shared strategy storage."""
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
# Try to use multiprocessing.Manager for process-safe storage
|
|
137
|
-
try:
|
|
138
|
-
if _strategy_manager is None:
|
|
139
|
-
_strategy_manager = multiprocessing.Manager()
|
|
140
|
-
_shared_strategy = _strategy_manager.dict()
|
|
141
|
-
_shared_strategy['active_strategy'] = None
|
|
142
|
-
_shared_strategy['last_check_time'] = 0
|
|
143
|
-
_shared_strategy['pending_strategy'] = None
|
|
144
|
-
_shared_strategy['query_strategy_map'] = _strategy_manager.dict()
|
|
145
|
-
_shared_strategy['last_transition_time'] = 0
|
|
146
|
-
_shared_strategy['session_invalidated'] = False
|
|
147
|
-
return _shared_strategy
|
|
148
|
-
except:
|
|
149
|
-
# Fall back to thread-local storage if Manager fails
|
|
150
|
-
return _local_strategy_cache
|
|
142
|
+
return _local_strategy_cache
|
|
151
143
|
|
|
152
144
|
|
|
153
145
|
def _get_active_strategy():
|
|
@@ -175,10 +167,14 @@ def _set_active_strategy(strategy):
|
|
|
175
167
|
with _strategy_lock:
|
|
176
168
|
shared_strategy = _get_shared_strategy()
|
|
177
169
|
current_time = time.time()
|
|
170
|
+
old_strategy = shared_strategy.get('active_strategy')
|
|
178
171
|
|
|
179
172
|
# Only update transition time if strategy actually changed
|
|
180
|
-
if
|
|
173
|
+
if old_strategy != normalized_strategy:
|
|
181
174
|
shared_strategy['last_transition_time'] = current_time
|
|
175
|
+
_strategy_debug_log(f"Setting active strategy: {old_strategy} -> {normalized_strategy}")
|
|
176
|
+
else:
|
|
177
|
+
_strategy_debug_log(f"Active strategy unchanged: {normalized_strategy}")
|
|
182
178
|
|
|
183
179
|
shared_strategy['active_strategy'] = normalized_strategy
|
|
184
180
|
shared_strategy['last_check_time'] = current_time
|
|
@@ -188,6 +184,9 @@ def _clear_strategy_cache():
|
|
|
188
184
|
"""Clear the cached strategy to force re-detection."""
|
|
189
185
|
with _strategy_lock:
|
|
190
186
|
shared_strategy = _get_shared_strategy()
|
|
187
|
+
old_strategy = shared_strategy.get('active_strategy')
|
|
188
|
+
if old_strategy:
|
|
189
|
+
_strategy_debug_log(f"Clearing strategy cache (was: {old_strategy})")
|
|
191
190
|
shared_strategy['active_strategy'] = None
|
|
192
191
|
shared_strategy['last_check_time'] = 0
|
|
193
192
|
shared_strategy['pending_strategy'] = None
|
|
@@ -208,6 +207,8 @@ def _set_pending_strategy(strategy):
|
|
|
208
207
|
|
|
209
208
|
if normalized_strategy != current_active:
|
|
210
209
|
shared_strategy['pending_strategy'] = normalized_strategy
|
|
210
|
+
query_count = len(shared_strategy.get('query_strategy_map', {}))
|
|
211
|
+
_strategy_debug_log(f"Setting pending strategy: {normalized_strategy} (current: {current_active}, active queries: {query_count})")
|
|
211
212
|
|
|
212
213
|
|
|
213
214
|
def _apply_pending_strategy():
|
|
@@ -219,11 +220,15 @@ def _apply_pending_strategy():
|
|
|
219
220
|
new_strategy = shared_strategy['pending_strategy']
|
|
220
221
|
current_time = time.time()
|
|
221
222
|
|
|
223
|
+
_strategy_debug_log(f"Applying pending strategy: {old_strategy} -> {new_strategy}")
|
|
224
|
+
|
|
222
225
|
shared_strategy['active_strategy'] = new_strategy
|
|
223
226
|
shared_strategy['pending_strategy'] = None
|
|
224
227
|
shared_strategy['last_check_time'] = current_time
|
|
225
228
|
shared_strategy['last_transition_time'] = current_time
|
|
226
229
|
shared_strategy['session_invalidated'] = True # Invalidate all sessions
|
|
230
|
+
|
|
231
|
+
_strategy_debug_log(f"Strategy transition complete. All sessions invalidated.")
|
|
227
232
|
|
|
228
233
|
return new_strategy
|
|
229
234
|
return None
|
|
@@ -250,6 +255,7 @@ def _register_query_strategy(query_id, strategy):
|
|
|
250
255
|
query_map = shared_strategy.get('query_strategy_map', {})
|
|
251
256
|
query_map[query_id] = normalized_strategy
|
|
252
257
|
shared_strategy['query_strategy_map'] = query_map
|
|
258
|
+
_strategy_debug_log(f"Query {query_id} registered with strategy: {normalized_strategy}")
|
|
253
259
|
|
|
254
260
|
|
|
255
261
|
def _get_query_strategy(query_id):
|
|
@@ -271,8 +277,11 @@ def _cleanup_query_strategy(query_id):
|
|
|
271
277
|
shared_strategy = _get_shared_strategy()
|
|
272
278
|
query_map = shared_strategy.get('query_strategy_map', {})
|
|
273
279
|
if query_id in query_map:
|
|
280
|
+
strategy = query_map[query_id]
|
|
274
281
|
del query_map[query_id]
|
|
275
282
|
shared_strategy['query_strategy_map'] = query_map
|
|
283
|
+
remaining_queries = len(query_map)
|
|
284
|
+
_strategy_debug_log(f"Query {query_id} completed (was using {strategy}). Remaining active queries: {remaining_queries}")
|
|
276
285
|
|
|
277
286
|
|
|
278
287
|
def _get_strategy_debug_info():
|
|
@@ -317,7 +326,9 @@ class Connection(object):
|
|
|
317
326
|
cluster_name: str = None,
|
|
318
327
|
secure: bool = False,
|
|
319
328
|
auto_resume: bool = True,
|
|
329
|
+
scheme: str = 'e6data',
|
|
320
330
|
grpc_options: dict = None,
|
|
331
|
+
debug: bool = False,
|
|
321
332
|
):
|
|
322
333
|
"""
|
|
323
334
|
Parameters
|
|
@@ -340,6 +351,8 @@ class Connection(object):
|
|
|
340
351
|
Flag to use a secure channel for data transfer
|
|
341
352
|
auto_resume: bool, Optional
|
|
342
353
|
Flag to enable auto resume of the cluster before the query execution
|
|
354
|
+
scheme: string, Optional
|
|
355
|
+
e6data supported scheme
|
|
343
356
|
grpc_options: dict, Optional
|
|
344
357
|
Specify gRPC configuration
|
|
345
358
|
- keepalive_timeout_ms: This parameter defines the time, in milliseconds, that a gRPC connection should remain idle before sending a keepalive ping to check if the connection is still alive.
|
|
@@ -347,6 +360,8 @@ class Connection(object):
|
|
|
347
360
|
- max_send_message_length: Similar to max_receive_message_length, this parameter sets the maximum allowed size (in bytes) for outgoing messages from the gRPC client
|
|
348
361
|
- grpc_prepare_timeout: Timeout for prepare statement API call (default to 10 minutes).
|
|
349
362
|
- keepalive_time_ms: This parameter defines the time, in milliseconds, Default to 30 seconds
|
|
363
|
+
debug: bool, Optional
|
|
364
|
+
Flag to enable debug logging for blue-green deployment strategy changes
|
|
350
365
|
"""
|
|
351
366
|
if not username or not password:
|
|
352
367
|
raise ValueError("username or password cannot be empty.")
|
|
@@ -376,6 +391,13 @@ class Connection(object):
|
|
|
376
391
|
The default maximum time on client side to wait for the cluster to resume is 5 minutes.
|
|
377
392
|
"""
|
|
378
393
|
self.grpc_auto_resume_timeout_seconds = self._grpc_options.pop('grpc_auto_resume_timeout_seconds')
|
|
394
|
+
|
|
395
|
+
# Store debug flag and register with debug connections
|
|
396
|
+
self._debug = debug
|
|
397
|
+
if self._debug:
|
|
398
|
+
_debug_connections.add(id(self))
|
|
399
|
+
_strategy_debug_log(f"Debug mode enabled for connection {id(self)}")
|
|
400
|
+
|
|
379
401
|
self._create_client()
|
|
380
402
|
|
|
381
403
|
@property
|
|
@@ -502,6 +524,7 @@ class Connection(object):
|
|
|
502
524
|
|
|
503
525
|
if active_strategy and not pending_strategy:
|
|
504
526
|
# Use cached strategy only if there's no pending strategy
|
|
527
|
+
_strategy_debug_log(f"Authenticating with cached strategy: {active_strategy}")
|
|
505
528
|
try:
|
|
506
529
|
authenticate_response = self._client.authenticate(
|
|
507
530
|
authenticate_request,
|
|
@@ -515,10 +538,15 @@ class Connection(object):
|
|
|
515
538
|
new_strategy = authenticate_response.new_strategy.lower()
|
|
516
539
|
if new_strategy != active_strategy:
|
|
517
540
|
_set_pending_strategy(new_strategy)
|
|
518
|
-
|
|
541
|
+
_apply_pending_strategy()
|
|
542
|
+
self._session_id = None
|
|
543
|
+
self.close()
|
|
544
|
+
self._create_client()
|
|
545
|
+
return self.get_session_id
|
|
519
546
|
except _InactiveRpcError as e:
|
|
520
547
|
if e.code() == grpc.StatusCode.UNKNOWN and 'status: 456' in e.details():
|
|
521
548
|
# Strategy changed, clear cache and retry
|
|
549
|
+
_strategy_debug_log(f"Got 456 error with strategy {active_strategy}, clearing cache and retrying")
|
|
522
550
|
_clear_strategy_cache()
|
|
523
551
|
active_strategy = None
|
|
524
552
|
else:
|
|
@@ -537,8 +565,10 @@ class Connection(object):
|
|
|
537
565
|
else:
|
|
538
566
|
# Always try blue first, then green if it fails with 456
|
|
539
567
|
strategies = ['blue', 'green']
|
|
568
|
+
_strategy_debug_log(f"No cached strategy, will try strategies in order: {strategies}")
|
|
540
569
|
last_error = None
|
|
541
570
|
for strategy in strategies:
|
|
571
|
+
_strategy_debug_log(f"Attempting authentication with strategy: {strategy}")
|
|
542
572
|
try:
|
|
543
573
|
authenticate_response = self._client.authenticate(
|
|
544
574
|
authenticate_request,
|
|
@@ -547,19 +577,24 @@ class Connection(object):
|
|
|
547
577
|
self._session_id = authenticate_response.sessionId
|
|
548
578
|
if self._session_id:
|
|
549
579
|
# Success! Cache this strategy
|
|
580
|
+
_strategy_debug_log(f"Authentication successful with strategy: {strategy}")
|
|
550
581
|
_set_active_strategy(strategy)
|
|
551
582
|
|
|
552
583
|
# Check for new strategy in authenticate response
|
|
553
|
-
if hasattr(authenticate_response,
|
|
554
|
-
'new_strategy') and authenticate_response.new_strategy:
|
|
584
|
+
if hasattr(authenticate_response, 'new_strategy') and authenticate_response.new_strategy:
|
|
555
585
|
new_strategy = authenticate_response.new_strategy.lower()
|
|
556
586
|
if new_strategy != strategy:
|
|
557
587
|
_set_pending_strategy(new_strategy)
|
|
558
|
-
|
|
588
|
+
_apply_pending_strategy()
|
|
589
|
+
self.close()
|
|
590
|
+
self._create_client()
|
|
591
|
+
self._session_id = None
|
|
592
|
+
return self.get_session_id
|
|
559
593
|
break
|
|
560
594
|
except _InactiveRpcError as e:
|
|
561
595
|
if e.code() == grpc.StatusCode.UNKNOWN and 'status: 456' in e.details():
|
|
562
596
|
# Wrong strategy, try the next one
|
|
597
|
+
_strategy_debug_log(f"Strategy {strategy} failed with 456 error, trying next")
|
|
563
598
|
last_error = e
|
|
564
599
|
continue
|
|
565
600
|
else:
|
|
@@ -586,21 +621,6 @@ class Connection(object):
|
|
|
586
621
|
).resume()
|
|
587
622
|
if status:
|
|
588
623
|
return self.get_session_id
|
|
589
|
-
# authenticate_request = e6x_engine_pb2.AuthenticateRequest(
|
|
590
|
-
# user=self.__username,
|
|
591
|
-
# password=self.__password
|
|
592
|
-
# )
|
|
593
|
-
# authenticate_response = self._client.authenticate(
|
|
594
|
-
# authenticate_request,
|
|
595
|
-
# metadata=_get_grpc_header(cluster=self.cluster_uuid, strategy=_get_active_strategy())
|
|
596
|
-
# )
|
|
597
|
-
# self._session_id = authenticate_response.sessionId
|
|
598
|
-
# # Check for new strategy in authenticate response
|
|
599
|
-
# if hasattr(authenticate_response, 'new_strategy') and authenticate_response.new_strategy:
|
|
600
|
-
# new_strategy = authenticate_response.new_strategy.lower()
|
|
601
|
-
# if new_strategy != _get_active_strategy():
|
|
602
|
-
# _set_pending_strategy(new_strategy)
|
|
603
|
-
# # Don't apply immediately - let new queries use fresh connections
|
|
604
624
|
else:
|
|
605
625
|
raise e
|
|
606
626
|
else:
|
|
@@ -646,6 +666,11 @@ class Connection(object):
|
|
|
646
666
|
self._channel.close()
|
|
647
667
|
self._channel = None
|
|
648
668
|
self._session_id = None
|
|
669
|
+
|
|
670
|
+
# Remove from debug connections if debug was enabled
|
|
671
|
+
if self._debug:
|
|
672
|
+
_debug_connections.discard(id(self))
|
|
673
|
+
_strategy_debug_log(f"Debug mode disabled for connection {id(self)}")
|
|
649
674
|
|
|
650
675
|
def check_connection(self):
|
|
651
676
|
"""
|
|
@@ -1092,6 +1117,7 @@ class Cursor(DBAPICursor):
|
|
|
1092
1117
|
query_map = shared_strategy.get('query_strategy_map', {})
|
|
1093
1118
|
|
|
1094
1119
|
if pending_strategy and len(query_map) == 0:
|
|
1120
|
+
_strategy_debug_log(f"Last query cleared, triggering pending strategy transition")
|
|
1095
1121
|
_apply_pending_strategy()
|
|
1096
1122
|
|
|
1097
1123
|
return clear_response
|
|
@@ -1115,6 +1141,7 @@ class Cursor(DBAPICursor):
|
|
|
1115
1141
|
query_map = shared_strategy.get('query_strategy_map', {})
|
|
1116
1142
|
|
|
1117
1143
|
if pending_strategy and len(query_map) == 0:
|
|
1144
|
+
_strategy_debug_log(f"Last query cleared, triggering pending strategy transition")
|
|
1118
1145
|
_apply_pending_strategy()
|
|
1119
1146
|
|
|
1120
1147
|
def status(self, query_id):
|
|
@@ -40,24 +40,7 @@ def _initialize_shared_state():
|
|
|
40
40
|
with _initialization_lock:
|
|
41
41
|
if _shared_strategy is not None:
|
|
42
42
|
return _shared_strategy
|
|
43
|
-
|
|
44
|
-
try:
|
|
45
|
-
# Try to create multiprocessing Manager
|
|
46
|
-
_manager = multiprocessing.Manager()
|
|
47
|
-
_shared_strategy = _manager.dict({
|
|
48
|
-
'active_strategy': None,
|
|
49
|
-
'last_check_time': 0,
|
|
50
|
-
'pending_strategy': None,
|
|
51
|
-
'query_strategy_map': _manager.dict(),
|
|
52
|
-
'last_transition_time': 0,
|
|
53
|
-
'session_invalidated': False
|
|
54
|
-
})
|
|
55
|
-
_logger.debug("Successfully initialized multiprocessing Manager for strategy sharing")
|
|
56
|
-
return _shared_strategy
|
|
57
|
-
except Exception as e:
|
|
58
|
-
# Fall back to thread-local storage if Manager fails
|
|
59
|
-
_logger.warning(f"Failed to initialize multiprocessing Manager: {e}. Using thread-local storage.")
|
|
60
|
-
return _local_strategy_cache
|
|
43
|
+
return _local_strategy_cache
|
|
61
44
|
|
|
62
45
|
|
|
63
46
|
def _get_shared_strategy():
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: e6data-python-connector
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.9
|
|
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.
|
|
@@ -38,7 +38,7 @@ Dynamic: summary
|
|
|
38
38
|
|
|
39
39
|
# e6data Python Connector
|
|
40
40
|
|
|
41
|
-

|
|
42
42
|
|
|
43
43
|
## Introduction
|
|
44
44
|
|
|
@@ -93,6 +93,55 @@ conn = Connection(
|
|
|
93
93
|
)
|
|
94
94
|
```
|
|
95
95
|
|
|
96
|
+
#### Connection Parameters
|
|
97
|
+
|
|
98
|
+
The `Connection` class supports the following parameters:
|
|
99
|
+
|
|
100
|
+
| Parameter | Type | Required | Default | Description |
|
|
101
|
+
|-----------|------|----------|---------|-------------|
|
|
102
|
+
| `host` | str | Yes | - | IP address or hostname of the e6data cluster |
|
|
103
|
+
| `port` | int | Yes | - | Port of the e6data engine (typically 80) |
|
|
104
|
+
| `username` | str | Yes | - | Your e6data Email ID |
|
|
105
|
+
| `password` | str | Yes | - | Access Token generated in the e6data console |
|
|
106
|
+
| `database` | str | No | None | Database to perform queries on |
|
|
107
|
+
| `catalog` | str | No | None | Catalog name |
|
|
108
|
+
| `cluster_name` | str | No | None | Name of the cluster for cluster-specific operations |
|
|
109
|
+
| `secure` | bool | No | False | Enable SSL/TLS for secure connections |
|
|
110
|
+
| `auto_resume` | bool | No | True | Automatically resume cluster if suspended |
|
|
111
|
+
| `grpc_options` | dict | No | None | Additional gRPC configuration options |
|
|
112
|
+
|
|
113
|
+
#### Secure Connection Example
|
|
114
|
+
|
|
115
|
+
To establish a secure connection using SSL/TLS:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
conn = Connection(
|
|
119
|
+
host=host,
|
|
120
|
+
port=443, # Typically 443 for secure connections
|
|
121
|
+
username=username,
|
|
122
|
+
password=password,
|
|
123
|
+
database=database,
|
|
124
|
+
cluster_name='production-cluster',
|
|
125
|
+
secure=True # Enable SSL/TLS
|
|
126
|
+
)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
#### Cluster-Specific Connection
|
|
130
|
+
|
|
131
|
+
When working with multiple clusters, specify the cluster name:
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
conn = Connection(
|
|
135
|
+
host=host,
|
|
136
|
+
port=port,
|
|
137
|
+
username=username,
|
|
138
|
+
password=password,
|
|
139
|
+
database=database,
|
|
140
|
+
cluster_name='analytics-cluster-01', # Specify cluster name
|
|
141
|
+
secure=True
|
|
142
|
+
)
|
|
143
|
+
```
|
|
144
|
+
|
|
96
145
|
### Perform a Queries & Get Results
|
|
97
146
|
|
|
98
147
|
```python
|
|
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.7rc2 → e6data_python_connector-2.3.9}/e6data_python_connector/common.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/e6data_python_connector/typeId.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/analyze_all_cases.py
RENAMED
|
File without changes
|
|
File without changes
|
{e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/analyze_correct_value.py
RENAMED
|
File without changes
|
|
File without changes
|
{e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/check_decimal_errors.py
RENAMED
|
File without changes
|
{e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/cleanup_test_files.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.7rc2 → e6data_python_connector-2.3.9}/test/test_all_decimal128_cases.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/test_comprehensive.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/test_decimal128_parsing.py
RENAMED
|
File without changes
|
|
File without changes
|
{e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/test_improved_parsing.py
RENAMED
|
File without changes
|
|
File without changes
|
{e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/test_manual_analysis.py
RENAMED
|
File without changes
|
|
File without changes
|
{e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/test_multiprocessing_fix.py
RENAMED
|
File without changes
|
{e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/test_new_implementation.py
RENAMED
|
File without changes
|
{e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/test_specific_binary.py
RENAMED
|
File without changes
|
|
File without changes
|
{e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/test_strategy_logic.py
RENAMED
|
File without changes
|
|
File without changes
|
{e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/test_strategy_sharing_fix.py
RENAMED
|
File without changes
|
{e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/test_user_binary_value.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/validate_decimal128.py
RENAMED
|
File without changes
|
{e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/validate_implementation.py
RENAMED
|
File without changes
|
{e6data_python_connector-2.3.7rc2 → e6data_python_connector-2.3.9}/test/verify_decimal_fix.py
RENAMED
|
File without changes
|