e6data-python-connector 2.2.1rc4__py3-none-any.whl → 2.2.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- e6data_python_connector/cluster_manager.py +164 -8
- e6data_python_connector/e6data_grpc.py +32 -5
- {e6data_python_connector-2.2.1rc4.dist-info → e6data_python_connector-2.2.2.dist-info}/METADATA +20 -11
- {e6data_python_connector-2.2.1rc4.dist-info → e6data_python_connector-2.2.2.dist-info}/RECORD +8 -8
- {e6data_python_connector-2.2.1rc4.dist-info → e6data_python_connector-2.2.2.dist-info}/WHEEL +1 -1
- {e6data_python_connector-2.2.1rc4.dist-info → e6data_python_connector-2.2.2.dist-info}/LICENSE +0 -0
- {e6data_python_connector-2.2.1rc4.dist-info → e6data_python_connector-2.2.2.dist-info}/entry_points.txt +0 -0
- {e6data_python_connector-2.2.1rc4.dist-info → e6data_python_connector-2.2.2.dist-info}/top_level.txt +0 -0
|
@@ -7,27 +7,97 @@ from grpc._channel import _InactiveRpcError
|
|
|
7
7
|
import multiprocessing
|
|
8
8
|
|
|
9
9
|
|
|
10
|
+
def _get_grpc_header(engine_ip=None, cluster=None):
|
|
11
|
+
metadata = []
|
|
12
|
+
if engine_ip:
|
|
13
|
+
metadata.append(('plannerip', engine_ip))
|
|
14
|
+
if cluster:
|
|
15
|
+
metadata.append(('cluster-uuid', cluster))
|
|
16
|
+
return metadata
|
|
17
|
+
|
|
18
|
+
|
|
10
19
|
class _StatusLock:
|
|
20
|
+
"""
|
|
21
|
+
A thread-safe and process-safe lock manager designed for managing
|
|
22
|
+
concurrent access protection in multithreaded and multiprocessing environments.
|
|
23
|
+
|
|
24
|
+
This class encapsulates locking mechanisms using threading and multiprocessing
|
|
25
|
+
modules to ensure an operation's atomicity and handle shared resources safely.
|
|
26
|
+
|
|
27
|
+
Attributes:
|
|
28
|
+
_LOCK_TIMEOUT (int): The maximum timeout (in milliseconds) to try
|
|
29
|
+
acquiring a lock before raising an error.
|
|
30
|
+
_status_thread_lock (threading.Lock): A thread-level lock to synchronize
|
|
31
|
+
access among threads in the same process.
|
|
32
|
+
_status_multiprocessing_lock (multiprocessing.Semaphore): A
|
|
33
|
+
process-level lock to synchronize access across different processes.
|
|
34
|
+
_is_active (bool): A boolean flag indicating whether the lock
|
|
35
|
+
is currently active (True) or not (False).
|
|
36
|
+
"""
|
|
37
|
+
|
|
11
38
|
_LOCK_TIMEOUT = 500
|
|
12
39
|
|
|
13
40
|
def __init__(self):
|
|
41
|
+
"""
|
|
42
|
+
Initializes the _StatusLock instance with its respective
|
|
43
|
+
thread and multiprocessing locks and sets the active flag to False.
|
|
44
|
+
"""
|
|
14
45
|
self._status_thread_lock = threading.Lock()
|
|
15
46
|
self._status_multiprocessing_lock = multiprocessing.Semaphore()
|
|
16
47
|
self._is_active = False
|
|
17
48
|
|
|
18
49
|
@property
|
|
19
50
|
def is_active(self) -> bool:
|
|
51
|
+
"""
|
|
52
|
+
Checks if the lock is currently active.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
bool: True if the lock is active, False otherwise.
|
|
56
|
+
"""
|
|
57
|
+
|
|
20
58
|
return self._is_active
|
|
21
59
|
|
|
22
60
|
def set_active(self):
|
|
61
|
+
"""
|
|
62
|
+
Activates the lock by setting the `_is_active` flag to True.
|
|
63
|
+
This can be used for manually marking the state of the lock
|
|
64
|
+
as active during synchronization operations.
|
|
65
|
+
"""
|
|
66
|
+
|
|
23
67
|
self._is_active = True
|
|
24
68
|
|
|
25
69
|
def __enter__(self):
|
|
70
|
+
"""
|
|
71
|
+
Enters a context-managed locking block.
|
|
72
|
+
|
|
73
|
+
Acquires both the thread-level lock and process-level semaphore
|
|
74
|
+
to ensure the current operation can safely access shared resources.
|
|
75
|
+
|
|
76
|
+
Raises:
|
|
77
|
+
TimeoutError: If the lock cannot be acquired within the timeout period.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
_StatusLock: The current instance of the lock, used for context management.
|
|
81
|
+
"""
|
|
82
|
+
|
|
26
83
|
self._status_thread_lock.acquire(timeout=self._LOCK_TIMEOUT)
|
|
27
84
|
self._status_multiprocessing_lock.acquire(timeout=self._LOCK_TIMEOUT)
|
|
28
85
|
return self
|
|
29
86
|
|
|
30
87
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
88
|
+
"""
|
|
89
|
+
Exits a context-managed locking block.
|
|
90
|
+
|
|
91
|
+
Releases both the thread-level lock and process-level semaphore
|
|
92
|
+
to allow other operations to acquire the lock. Handles cleanup
|
|
93
|
+
regardless of whether an exception occurred.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
exc_type (Type[BaseException]): The type of exception raised (if any).
|
|
97
|
+
exc_val (BaseException): The exception instance raised (if any).
|
|
98
|
+
exc_tb (Traceback): The traceback object of the exception (if any).
|
|
99
|
+
"""
|
|
100
|
+
|
|
31
101
|
self._status_thread_lock.release()
|
|
32
102
|
self._status_multiprocessing_lock.release()
|
|
33
103
|
|
|
@@ -36,16 +106,58 @@ status_lock = _StatusLock()
|
|
|
36
106
|
|
|
37
107
|
|
|
38
108
|
class ClusterManager:
|
|
39
|
-
|
|
109
|
+
"""
|
|
110
|
+
A manager for handling operations and connections with a remote cluster infrastructure.
|
|
111
|
+
Provides methods to manage cluster states, such as resuming or suspending its activity,
|
|
112
|
+
by interacting with a gRPC-based remote service.
|
|
113
|
+
|
|
114
|
+
Attributes:
|
|
115
|
+
_host (str): The hostname or IP address of the cluster service.
|
|
116
|
+
_port (int): The port number used to connect to the cluster service.
|
|
117
|
+
_user (str): The username for authentication with the cluster service.
|
|
118
|
+
_password (str): The password for authentication with the cluster service.
|
|
119
|
+
_timeout (float): The timeout threshold (as an epoch timestamp) for operations,
|
|
120
|
+
defaulting to 3 minutes from initialization.
|
|
121
|
+
_secure_channel (bool): Indicates whether a secure gRPC channel
|
|
122
|
+
(SSL/TLS) should be used for communication; defaults to False.
|
|
123
|
+
cluster_uuid (str): The unique identifier for the target cluster.
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
def __init__(self, host: str, port: int, user: str, password: str, secure_channel: bool = False, timeout=60 * 3, cluster_uuid=None):
|
|
127
|
+
"""
|
|
128
|
+
Initializes a new instance of the ClusterManager class.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
host (str): The hostname or IP address of the cluster service.
|
|
132
|
+
port (int): The port number for accessing the cluster service.
|
|
133
|
+
user (str): The username used for connecting to the cluster service.
|
|
134
|
+
password (str): The password used for connecting to the cluster service.
|
|
135
|
+
secure_channel (bool, optional): Whether to use a secure
|
|
136
|
+
gRPC channel for communication; defaults to False.
|
|
137
|
+
timeout (int, optional): The timeout duration (in seconds) for operations;
|
|
138
|
+
defaults to 3 minutes.
|
|
139
|
+
cluster_uuid (str, optional): The unique identifier for the target cluster;
|
|
140
|
+
defaults to None.
|
|
141
|
+
"""
|
|
142
|
+
|
|
40
143
|
self._host = host
|
|
41
144
|
self._port = port
|
|
42
145
|
self._user = user
|
|
43
146
|
self._password = password
|
|
44
147
|
self._timeout = time.time() + timeout
|
|
45
148
|
self._secure_channel = secure_channel
|
|
149
|
+
self.cluster_uuid = cluster_uuid
|
|
46
150
|
|
|
47
151
|
@property
|
|
48
152
|
def _get_connection(self):
|
|
153
|
+
"""
|
|
154
|
+
Dynamically establishes a gRPC connection to the cluster service.
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
cluster_pb2_grpc.ClusterServiceStub: A gRPC client stub
|
|
158
|
+
for interacting with the cluster service methods.
|
|
159
|
+
"""
|
|
160
|
+
|
|
49
161
|
if self._secure_channel:
|
|
50
162
|
self._channel = grpc.secure_channel(
|
|
51
163
|
target='{}:{}'.format(self._host, self._port),
|
|
@@ -59,37 +171,75 @@ class ClusterManager:
|
|
|
59
171
|
|
|
60
172
|
def resume(self) -> bool:
|
|
61
173
|
"""
|
|
62
|
-
|
|
174
|
+
Resumes the cluster if it is currently suspended or not in the 'active' state.
|
|
175
|
+
|
|
176
|
+
This method interacts with the remote cluster service to verify its current
|
|
177
|
+
status. If suspended, it sends a resume request and monitors
|
|
178
|
+
the cluster's state until it becomes active or fails.
|
|
179
|
+
|
|
180
|
+
The operation uses a locking mechanism (`status_lock`) to ensure thread-safe
|
|
181
|
+
and process-safe state transitions.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
bool: True if the cluster resumes successfully and becomes active;
|
|
185
|
+
False if the cluster cannot be resumed, remains suspended, or fails.
|
|
186
|
+
|
|
187
|
+
Raises:
|
|
188
|
+
_InactiveRpcError: An exception raised if there is a communication error
|
|
189
|
+
while interacting with the remote cluster service.
|
|
190
|
+
|
|
191
|
+
Notes:
|
|
192
|
+
- If the cluster is already active, this method completes successfully
|
|
193
|
+
without further actions.
|
|
194
|
+
- If the cluster is in a 'resuming' state, this method waits for the
|
|
195
|
+
cluster to transition to 'active' or any terminal state (e.g., 'failed').
|
|
196
|
+
- The operation is subject to the `_timeout` threshold;
|
|
197
|
+
if the timeout expires, the method returns False.
|
|
63
198
|
"""
|
|
199
|
+
|
|
64
200
|
with status_lock as lock:
|
|
65
201
|
if lock.is_active:
|
|
66
202
|
return True
|
|
203
|
+
|
|
204
|
+
# Retrieve the current cluster status
|
|
67
205
|
status_payload = cluster_pb2.ClusterStatusRequest(
|
|
68
206
|
user=self._user,
|
|
69
207
|
password=self._password
|
|
70
208
|
)
|
|
71
|
-
current_status = self._get_connection.status(
|
|
209
|
+
current_status = self._get_connection.status(
|
|
210
|
+
status_payload,
|
|
211
|
+
metadata=_get_grpc_header(cluster=self.cluster_uuid)
|
|
212
|
+
)
|
|
72
213
|
if current_status.status == 'suspended':
|
|
214
|
+
# Send the resume request
|
|
73
215
|
payload = cluster_pb2.ResumeRequest(
|
|
74
216
|
user=self._user,
|
|
75
217
|
password=self._password
|
|
76
218
|
)
|
|
77
|
-
response = self._get_connection.resume(
|
|
219
|
+
response = self._get_connection.resume(
|
|
220
|
+
payload,
|
|
221
|
+
metadata=_get_grpc_header(cluster=self.cluster_uuid)
|
|
222
|
+
)
|
|
78
223
|
elif current_status.status == 'active':
|
|
79
224
|
return True
|
|
80
225
|
elif current_status.status != 'resuming':
|
|
81
226
|
"""
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
227
|
+
If cluster cannot be resumed due to its current state,
|
|
228
|
+
or already in a process of resuming, terminate the operation.
|
|
229
|
+
"""
|
|
85
230
|
return False
|
|
231
|
+
|
|
232
|
+
# Wait for the cluster to become active
|
|
86
233
|
while True:
|
|
87
234
|
try:
|
|
88
235
|
status_payload = cluster_pb2.ClusterStatusRequest(
|
|
89
236
|
user=self._user,
|
|
90
237
|
password=self._password
|
|
91
238
|
)
|
|
92
|
-
response = self._get_connection.status(
|
|
239
|
+
response = self._get_connection.status(
|
|
240
|
+
status_payload,
|
|
241
|
+
metadata=_get_grpc_header(cluster=self.cluster_uuid)
|
|
242
|
+
)
|
|
93
243
|
if response.status == 'active':
|
|
94
244
|
lock.set_active()
|
|
95
245
|
return True
|
|
@@ -102,4 +252,10 @@ class ClusterManager:
|
|
|
102
252
|
time.sleep(5)
|
|
103
253
|
|
|
104
254
|
def suspend(self):
|
|
255
|
+
"""
|
|
256
|
+
Suspends the cluster operations (not implemented).
|
|
257
|
+
|
|
258
|
+
Placeholder method to be implemented for suspending a cluster,
|
|
259
|
+
typically interacting with the remote cluster service API.
|
|
260
|
+
"""
|
|
105
261
|
pass
|
|
@@ -53,14 +53,35 @@ def _parse_timestamp(value):
|
|
|
53
53
|
value = datetime.datetime.strptime(value, format)
|
|
54
54
|
else:
|
|
55
55
|
raise Exception(
|
|
56
|
-
'Cannot convert "{}" into a datetime'.format(value)
|
|
56
|
+
'Cannot convert "{}" into a datetime'.format(value)
|
|
57
|
+
)
|
|
57
58
|
else:
|
|
58
59
|
value = None
|
|
59
60
|
return value
|
|
60
61
|
|
|
61
62
|
|
|
62
|
-
TYPES_CONVERTER = {
|
|
63
|
-
|
|
63
|
+
TYPES_CONVERTER = {
|
|
64
|
+
"DECIMAL_TYPE": Decimal,
|
|
65
|
+
"TIMESTAMP_TYPE": _parse_timestamp
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def re_auth(func):
|
|
70
|
+
def wrapper(self, *args, **kwargs):
|
|
71
|
+
try:
|
|
72
|
+
return func(self, *args, **kwargs)
|
|
73
|
+
except _InactiveRpcError as e:
|
|
74
|
+
print(f'RE_AUTH: Function Name: {func}')
|
|
75
|
+
print(f'RE_AUTH: Error Found {e}')
|
|
76
|
+
if e.code() == grpc.StatusCode.INTERNAL and 'Access denied' in e.details():
|
|
77
|
+
print('RE_AUTH: Initialising re-authentication.')
|
|
78
|
+
self.connection.get_re_authenticate_session_id()
|
|
79
|
+
print(f'RE_AUTH: Re-auth successful.')
|
|
80
|
+
return func(self, *args, **kwargs)
|
|
81
|
+
else:
|
|
82
|
+
raise e
|
|
83
|
+
|
|
84
|
+
return wrapper
|
|
64
85
|
|
|
65
86
|
|
|
66
87
|
class HiveParamEscaper(ParamEscaper):
|
|
@@ -202,6 +223,10 @@ class Connection(object):
|
|
|
202
223
|
)
|
|
203
224
|
self._client = e6x_engine_pb2_grpc.QueryEngineServiceStub(self._channel)
|
|
204
225
|
|
|
226
|
+
def get_re_authenticate_session_id(self):
|
|
227
|
+
self._session_id = None
|
|
228
|
+
return self.get_session_id
|
|
229
|
+
|
|
205
230
|
@property
|
|
206
231
|
def get_session_id(self):
|
|
207
232
|
"""
|
|
@@ -228,7 +253,8 @@ class Connection(object):
|
|
|
228
253
|
port=self._port,
|
|
229
254
|
user=self.__username,
|
|
230
255
|
password=self.__password,
|
|
231
|
-
secure_channel=self._secure_channel
|
|
256
|
+
secure_channel=self._secure_channel,
|
|
257
|
+
cluster_uuid=self.cluster_uuid
|
|
232
258
|
).resume()
|
|
233
259
|
if status:
|
|
234
260
|
authenticate_request = e6x_engine_pb2.AuthenticateRequest(
|
|
@@ -496,6 +522,7 @@ class Cursor(DBAPICursor):
|
|
|
496
522
|
)
|
|
497
523
|
return self.connection.client.status(status_request, metadata=self.metadata)
|
|
498
524
|
|
|
525
|
+
@re_auth
|
|
499
526
|
def execute(self, operation, parameters=None, **kwargs):
|
|
500
527
|
"""Prepare and execute a database operation (query or command).
|
|
501
528
|
Return values are not defined.
|
|
@@ -551,6 +578,7 @@ class Cursor(DBAPICursor):
|
|
|
551
578
|
|
|
552
579
|
self._query_id = prepare_statement_response.queryId
|
|
553
580
|
self._engine_ip = prepare_statement_response.engineIP
|
|
581
|
+
|
|
554
582
|
execute_statement_request = e6x_engine_pb2.ExecuteStatementV2Request(
|
|
555
583
|
engineIP=self._engine_ip,
|
|
556
584
|
sessionId=self.connection.get_session_id,
|
|
@@ -635,7 +663,6 @@ class Cursor(DBAPICursor):
|
|
|
635
663
|
return self._fetch_all()
|
|
636
664
|
|
|
637
665
|
def fetchmany(self, size: int = None):
|
|
638
|
-
# _logger.info("fetching all from overriden method")
|
|
639
666
|
if size is None:
|
|
640
667
|
size = self.arraysize
|
|
641
668
|
if self._data is None:
|
{e6data_python_connector-2.2.1rc4.dist-info → e6data_python_connector-2.2.2.dist-info}/METADATA
RENAMED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: e6data-python-connector
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.2
|
|
4
4
|
Summary: Client for the e6data distributed SQL Engine.
|
|
5
5
|
Home-page: https://github.com/e6x-labs/e6data-python-connector
|
|
6
|
-
Author:
|
|
6
|
+
Author: e6data, Inc.
|
|
7
7
|
Author-email: info@e6data.com
|
|
8
8
|
License: Apache 2.0
|
|
9
9
|
Classifier: Operating System :: POSIX :: Linux
|
|
@@ -18,18 +18,27 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
20
|
License-File: LICENSE
|
|
21
|
-
Requires-Dist: sqlalchemy
|
|
22
|
-
Requires-Dist: future
|
|
23
|
-
Requires-Dist: python-dateutil
|
|
24
|
-
Requires-Dist: pycryptodome
|
|
25
|
-
Requires-Dist: pytz
|
|
26
|
-
Requires-Dist: thrift
|
|
27
|
-
Requires-Dist: grpcio
|
|
21
|
+
Requires-Dist: sqlalchemy>=1.0.0
|
|
22
|
+
Requires-Dist: future==1.0.0
|
|
23
|
+
Requires-Dist: python-dateutil==2.9.0.post0
|
|
24
|
+
Requires-Dist: pycryptodome==3.19.1
|
|
25
|
+
Requires-Dist: pytz==2024.1
|
|
26
|
+
Requires-Dist: thrift==0.20.0
|
|
27
|
+
Requires-Dist: grpcio==1.65.1
|
|
28
28
|
Requires-Dist: grpcio-tools
|
|
29
|
+
Dynamic: author
|
|
30
|
+
Dynamic: author-email
|
|
31
|
+
Dynamic: classifier
|
|
32
|
+
Dynamic: description
|
|
33
|
+
Dynamic: description-content-type
|
|
34
|
+
Dynamic: home-page
|
|
35
|
+
Dynamic: license
|
|
36
|
+
Dynamic: requires-dist
|
|
37
|
+
Dynamic: summary
|
|
29
38
|
|
|
30
39
|
# e6data Python Connector
|
|
31
40
|
|
|
32
|
-

|
|
33
42
|
|
|
34
43
|
## Introduction
|
|
35
44
|
|
{e6data_python_connector-2.2.1rc4.dist-info → e6data_python_connector-2.2.2.dist-info}/RECORD
RENAMED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
e6data_python_connector/__init__.py,sha256=x_VwhPQ7XLlthR-NJL9Vl7lkLBRCG2pSjmyZSBHOBpM,103
|
|
2
|
-
e6data_python_connector/cluster_manager.py,sha256=
|
|
2
|
+
e6data_python_connector/cluster_manager.py,sha256=ZKxsMDgUuEKuRxNxo6vFhV8q4-Il3Rg-hX8niaACMso,10163
|
|
3
3
|
e6data_python_connector/common.py,sha256=nk0CDQ5j1iu8DctBOXX4QypPYmxSgIgoA-Cn1UZXLic,9976
|
|
4
4
|
e6data_python_connector/constants.py,sha256=h5w0lWiFsxz__zUZRlo1f72-8mT_vt9gcWvtWh0viFI,682
|
|
5
5
|
e6data_python_connector/datainputstream.py,sha256=2XKW__PCGfTQ55Puqgie2LZdk_WU-YKwpEIUOQEF-sY,12592
|
|
6
6
|
e6data_python_connector/date_time_utils.py,sha256=zmIwRhEGJwDvCOUjAl6VTDWGppluA-DPl_ioHTNpJKI,14413
|
|
7
7
|
e6data_python_connector/dialect.py,sha256=hkVRjXxx9DhS8-aHpSoyMVhhHxVs3jdTE60x2aOAf5I,11312
|
|
8
|
-
e6data_python_connector/e6data_grpc.py,sha256=
|
|
8
|
+
e6data_python_connector/e6data_grpc.py,sha256=B5MsAKsI6Syx9QGDpDOXxQn4tjEwiByA4HevrSczbFs,26981
|
|
9
9
|
e6data_python_connector/exceptions.py,sha256=vrUqdzMq3LjbCmvr5_TcRq9eKmdqGfe-uVjR0BJQf5U,382
|
|
10
10
|
e6data_python_connector/typeId.py,sha256=2EMNWK2SO5CBReNuJphma6xp-69r9GDwwys66t-kSk0,1777
|
|
11
11
|
e6data_python_connector/cluster_server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -21,9 +21,9 @@ e6data_python_connector/server/e6x_engine_pb2.py,sha256=BxJjbijNrEqXQTWEhzLm54Fq
|
|
|
21
21
|
e6data_python_connector/server/e6x_engine_pb2.pyi,sha256=_aQDLFbSCQ7ubK0Z5ZKh9_VAHFaXbMV55MXYXr85srg,18693
|
|
22
22
|
e6data_python_connector/server/e6x_engine_pb2_grpc.py,sha256=n-eTCsAjsmpgPA_bDansKZq_zMmqkK6TlQGn6IdT6L4,46671
|
|
23
23
|
e6data_python_connector/server/ttypes.py,sha256=uWxwSp4m6e-7SeSEckbZtSduGYmfzXWM09iy5eltBPA,21227
|
|
24
|
-
e6data_python_connector-2.2.
|
|
25
|
-
e6data_python_connector-2.2.
|
|
26
|
-
e6data_python_connector-2.2.
|
|
27
|
-
e6data_python_connector-2.2.
|
|
28
|
-
e6data_python_connector-2.2.
|
|
29
|
-
e6data_python_connector-2.2.
|
|
24
|
+
e6data_python_connector-2.2.2.dist-info/LICENSE,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
|
|
25
|
+
e6data_python_connector-2.2.2.dist-info/METADATA,sha256=SDpjML-yRccpZOxJSqLr_L1O9Ph7YHkokCdwCYrjRzk,7309
|
|
26
|
+
e6data_python_connector-2.2.2.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
|
|
27
|
+
e6data_python_connector-2.2.2.dist-info/entry_points.txt,sha256=MDK4cAOND0preLvq2Tlpnz00wXnP_Y_UopqJsAf3U7Y,77
|
|
28
|
+
e6data_python_connector-2.2.2.dist-info/top_level.txt,sha256=ChOS6qLf-SQycXr0uBiVxomIfYfbH2SgwrOWhW79ab0,24
|
|
29
|
+
e6data_python_connector-2.2.2.dist-info/RECORD,,
|
{e6data_python_connector-2.2.1rc4.dist-info → e6data_python_connector-2.2.2.dist-info}/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|
{e6data_python_connector-2.2.1rc4.dist-info → e6data_python_connector-2.2.2.dist-info}/top_level.txt
RENAMED
|
File without changes
|