e6data-python-connector 2.2.1rc4__tar.gz → 2.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. {e6data_python_connector-2.2.1rc4/e6data_python_connector.egg-info → e6data_python_connector-2.2.2}/PKG-INFO +19 -10
  2. {e6data_python_connector-2.2.1rc4 → e6data_python_connector-2.2.2}/README.md +1 -1
  3. e6data_python_connector-2.2.2/e6data_python_connector/cluster_manager.py +261 -0
  4. {e6data_python_connector-2.2.1rc4 → e6data_python_connector-2.2.2}/e6data_python_connector/e6data_grpc.py +32 -5
  5. {e6data_python_connector-2.2.1rc4 → e6data_python_connector-2.2.2/e6data_python_connector.egg-info}/PKG-INFO +19 -10
  6. e6data_python_connector-2.2.2/e6data_python_connector.egg-info/requires.txt +8 -0
  7. {e6data_python_connector-2.2.1rc4 → e6data_python_connector-2.2.2}/setup.py +23 -14
  8. e6data_python_connector-2.2.1rc4/e6data_python_connector/cluster_manager.py +0 -105
  9. e6data_python_connector-2.2.1rc4/e6data_python_connector.egg-info/requires.txt +0 -8
  10. {e6data_python_connector-2.2.1rc4 → e6data_python_connector-2.2.2}/LICENSE +0 -0
  11. {e6data_python_connector-2.2.1rc4 → e6data_python_connector-2.2.2}/MANIFEST.in +0 -0
  12. {e6data_python_connector-2.2.1rc4 → e6data_python_connector-2.2.2}/e6data_python_connector/__init__.py +0 -0
  13. {e6data_python_connector-2.2.1rc4 → e6data_python_connector-2.2.2}/e6data_python_connector/cluster_server/__init__.py +0 -0
  14. {e6data_python_connector-2.2.1rc4 → e6data_python_connector-2.2.2}/e6data_python_connector/cluster_server/cluster_pb2.py +0 -0
  15. {e6data_python_connector-2.2.1rc4 → e6data_python_connector-2.2.2}/e6data_python_connector/cluster_server/cluster_pb2_grpc.py +0 -0
  16. {e6data_python_connector-2.2.1rc4 → e6data_python_connector-2.2.2}/e6data_python_connector/common.py +0 -0
  17. {e6data_python_connector-2.2.1rc4 → e6data_python_connector-2.2.2}/e6data_python_connector/constants.py +0 -0
  18. {e6data_python_connector-2.2.1rc4 → e6data_python_connector-2.2.2}/e6data_python_connector/datainputstream.py +0 -0
  19. {e6data_python_connector-2.2.1rc4 → e6data_python_connector-2.2.2}/e6data_python_connector/date_time_utils.py +0 -0
  20. {e6data_python_connector-2.2.1rc4 → e6data_python_connector-2.2.2}/e6data_python_connector/dialect.py +0 -0
  21. {e6data_python_connector-2.2.1rc4 → e6data_python_connector-2.2.2}/e6data_python_connector/e6x_vector/__init__.py +0 -0
  22. {e6data_python_connector-2.2.1rc4 → e6data_python_connector-2.2.2}/e6data_python_connector/e6x_vector/constants.py +0 -0
  23. {e6data_python_connector-2.2.1rc4 → e6data_python_connector-2.2.2}/e6data_python_connector/e6x_vector/ttypes.py +0 -0
  24. {e6data_python_connector-2.2.1rc4 → e6data_python_connector-2.2.2}/e6data_python_connector/exceptions.py +0 -0
  25. {e6data_python_connector-2.2.1rc4 → e6data_python_connector-2.2.2}/e6data_python_connector/server/QueryEngineService.py +0 -0
  26. {e6data_python_connector-2.2.1rc4 → e6data_python_connector-2.2.2}/e6data_python_connector/server/__init__.py +0 -0
  27. {e6data_python_connector-2.2.1rc4 → e6data_python_connector-2.2.2}/e6data_python_connector/server/constants.py +0 -0
  28. {e6data_python_connector-2.2.1rc4 → e6data_python_connector-2.2.2}/e6data_python_connector/server/e6x_engine_pb2.py +0 -0
  29. {e6data_python_connector-2.2.1rc4 → e6data_python_connector-2.2.2}/e6data_python_connector/server/e6x_engine_pb2.pyi +0 -0
  30. {e6data_python_connector-2.2.1rc4 → e6data_python_connector-2.2.2}/e6data_python_connector/server/e6x_engine_pb2_grpc.py +0 -0
  31. {e6data_python_connector-2.2.1rc4 → e6data_python_connector-2.2.2}/e6data_python_connector/server/ttypes.py +0 -0
  32. {e6data_python_connector-2.2.1rc4 → e6data_python_connector-2.2.2}/e6data_python_connector/typeId.py +0 -0
  33. {e6data_python_connector-2.2.1rc4 → e6data_python_connector-2.2.2}/e6data_python_connector.egg-info/SOURCES.txt +0 -0
  34. {e6data_python_connector-2.2.1rc4 → e6data_python_connector-2.2.2}/e6data_python_connector.egg-info/dependency_links.txt +0 -0
  35. {e6data_python_connector-2.2.1rc4 → e6data_python_connector-2.2.2}/e6data_python_connector.egg-info/entry_points.txt +0 -0
  36. {e6data_python_connector-2.2.1rc4 → e6data_python_connector-2.2.2}/e6data_python_connector.egg-info/top_level.txt +0 -0
  37. {e6data_python_connector-2.2.1rc4 → e6data_python_connector-2.2.2}/pyproject.toml +0 -0
  38. {e6data_python_connector-2.2.1rc4 → e6data_python_connector-2.2.2}/setup.cfg +0 -0
@@ -1,9 +1,9 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: e6data-python-connector
3
- Version: 2.2.1rc4
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: Uniphi, Inc.
6
+ Author: e6data, Inc.
7
7
  Author-email: info@e6data.com
8
8
  License: Apache 2.0
9
9
  Classifier: Operating System :: POSIX :: Linux
@@ -19,17 +19,26 @@ Classifier: Programming Language :: Python :: 3.12
19
19
  Description-Content-Type: text/markdown
20
20
  License-File: LICENSE
21
21
  Requires-Dist: sqlalchemy>=1.0.0
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>=1.65.1
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
- ![version](https://img.shields.io/badge/version-2.2.1rc4-blue.svg)
41
+ ![version](https://img.shields.io/badge/version-2.2.2-blue.svg)
33
42
 
34
43
  ## Introduction
35
44
 
@@ -1,6 +1,6 @@
1
1
  # e6data Python Connector
2
2
 
3
- ![version](https://img.shields.io/badge/version-2.2.1rc4-blue.svg)
3
+ ![version](https://img.shields.io/badge/version-2.2.2-blue.svg)
4
4
 
5
5
  ## Introduction
6
6
 
@@ -0,0 +1,261 @@
1
+ import threading
2
+ import time
3
+ import e6data_python_connector.cluster_server.cluster_pb2 as cluster_pb2
4
+ import e6data_python_connector.cluster_server.cluster_pb2_grpc as cluster_pb2_grpc
5
+ import grpc
6
+ from grpc._channel import _InactiveRpcError
7
+ import multiprocessing
8
+
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
+
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
+
38
+ _LOCK_TIMEOUT = 500
39
+
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
+ """
45
+ self._status_thread_lock = threading.Lock()
46
+ self._status_multiprocessing_lock = multiprocessing.Semaphore()
47
+ self._is_active = False
48
+
49
+ @property
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
+
58
+ return self._is_active
59
+
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
+
67
+ self._is_active = True
68
+
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
+
83
+ self._status_thread_lock.acquire(timeout=self._LOCK_TIMEOUT)
84
+ self._status_multiprocessing_lock.acquire(timeout=self._LOCK_TIMEOUT)
85
+ return self
86
+
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
+
101
+ self._status_thread_lock.release()
102
+ self._status_multiprocessing_lock.release()
103
+
104
+
105
+ status_lock = _StatusLock()
106
+
107
+
108
+ class ClusterManager:
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
+
143
+ self._host = host
144
+ self._port = port
145
+ self._user = user
146
+ self._password = password
147
+ self._timeout = time.time() + timeout
148
+ self._secure_channel = secure_channel
149
+ self.cluster_uuid = cluster_uuid
150
+
151
+ @property
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
+
161
+ if self._secure_channel:
162
+ self._channel = grpc.secure_channel(
163
+ target='{}:{}'.format(self._host, self._port),
164
+ credentials=grpc.ssl_channel_credentials()
165
+ )
166
+ else:
167
+ self._channel = grpc.insecure_channel(
168
+ target='{}:{}'.format(self._host, self._port)
169
+ )
170
+ return cluster_pb2_grpc.ClusterServiceStub(self._channel)
171
+
172
+ def resume(self) -> bool:
173
+ """
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.
198
+ """
199
+
200
+ with status_lock as lock:
201
+ if lock.is_active:
202
+ return True
203
+
204
+ # Retrieve the current cluster status
205
+ status_payload = cluster_pb2.ClusterStatusRequest(
206
+ user=self._user,
207
+ password=self._password
208
+ )
209
+ current_status = self._get_connection.status(
210
+ status_payload,
211
+ metadata=_get_grpc_header(cluster=self.cluster_uuid)
212
+ )
213
+ if current_status.status == 'suspended':
214
+ # Send the resume request
215
+ payload = cluster_pb2.ResumeRequest(
216
+ user=self._user,
217
+ password=self._password
218
+ )
219
+ response = self._get_connection.resume(
220
+ payload,
221
+ metadata=_get_grpc_header(cluster=self.cluster_uuid)
222
+ )
223
+ elif current_status.status == 'active':
224
+ return True
225
+ elif current_status.status != 'resuming':
226
+ """
227
+ If cluster cannot be resumed due to its current state,
228
+ or already in a process of resuming, terminate the operation.
229
+ """
230
+ return False
231
+
232
+ # Wait for the cluster to become active
233
+ while True:
234
+ try:
235
+ status_payload = cluster_pb2.ClusterStatusRequest(
236
+ user=self._user,
237
+ password=self._password
238
+ )
239
+ response = self._get_connection.status(
240
+ status_payload,
241
+ metadata=_get_grpc_header(cluster=self.cluster_uuid)
242
+ )
243
+ if response.status == 'active':
244
+ lock.set_active()
245
+ return True
246
+ if response.status in ['suspended', 'failed']:
247
+ return False
248
+ if time.time() > self._timeout:
249
+ return False
250
+ except _InactiveRpcError as e:
251
+ pass
252
+ time.sleep(5)
253
+
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
+ """
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 = {"DECIMAL_TYPE": Decimal,
63
- "TIMESTAMP_TYPE": _parse_timestamp}
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:
@@ -1,9 +1,9 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: e6data-python-connector
3
- Version: 2.2.1rc4
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: Uniphi, Inc.
6
+ Author: e6data, Inc.
7
7
  Author-email: info@e6data.com
8
8
  License: Apache 2.0
9
9
  Classifier: Operating System :: POSIX :: Linux
@@ -19,17 +19,26 @@ Classifier: Programming Language :: Python :: 3.12
19
19
  Description-Content-Type: text/markdown
20
20
  License-File: LICENSE
21
21
  Requires-Dist: sqlalchemy>=1.0.0
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>=1.65.1
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
- ![version](https://img.shields.io/badge/version-2.2.1rc4-blue.svg)
41
+ ![version](https://img.shields.io/badge/version-2.2.2-blue.svg)
33
42
 
34
43
  ## Introduction
35
44
 
@@ -0,0 +1,8 @@
1
+ sqlalchemy>=1.0.0
2
+ future==1.0.0
3
+ python-dateutil==2.9.0.post0
4
+ pycryptodome==3.19.1
5
+ pytz==2024.1
6
+ thrift==0.20.0
7
+ grpcio==1.65.1
8
+ grpcio-tools
@@ -12,35 +12,44 @@
12
12
 
13
13
  import setuptools
14
14
 
15
- VERSION = [2, 2, 1, 'rc4']
15
+ VERSION = (2, 2, 2)
16
16
 
17
17
 
18
18
  def get_long_desc():
19
- with open("README.md", "r") as fh:
20
- long_description = fh.read()
21
- return long_description
19
+ """
20
+ Reads the `README.md` file to provide a detailed description of the package.
21
+
22
+ Returns:
23
+ str: The content of the README.md file as a string.
24
+ """
25
+ try:
26
+ with open("README.md", "r", encoding="utf-8") as fh: # Explicitly specifying encoding.
27
+ return fh.read()
28
+ except FileNotFoundError:
29
+ # Fallback if README.md is missing.
30
+ return "Client for the e6data distributed SQL Engine."
22
31
 
23
32
 
24
33
  setuptools.setup(
25
34
  name="e6data-python-connector",
26
- version='.'.join('%s' % v for v in VERSION),
27
- author="Uniphi, Inc.",
35
+ version=".".join(map(str, VERSION)), # More Pythonic way to convert tuple to string.
36
+ author="e6data, Inc.",
28
37
  author_email="info@e6data.com",
29
38
  description="Client for the e6data distributed SQL Engine.",
30
39
  long_description=get_long_desc(),
31
40
  long_description_content_type="text/markdown",
32
41
  url='https://github.com/e6x-labs/e6data-python-connector',
33
- packages=setuptools.find_packages(),
42
+ packages=setuptools.find_packages(), # Automatically detects all packages in the source tree.
34
43
  license="Apache 2.0",
35
- include_package_data=True,
44
+ include_package_data=True, # Includes non-Python files specified in MANIFEST.in.
36
45
  install_requires=[
37
46
  'sqlalchemy>=1.0.0',
38
- 'future',
39
- 'python-dateutil',
40
- 'pycryptodome',
41
- 'pytz',
42
- 'thrift',
43
- 'grpcio>=1.65.1',
47
+ 'future==1.0.0',
48
+ 'python-dateutil==2.9.0.post0',
49
+ 'pycryptodome==3.19.1',
50
+ 'pytz==2024.1',
51
+ 'thrift==0.20.0',
52
+ 'grpcio==1.65.1',
44
53
  'grpcio-tools',
45
54
  ],
46
55
  classifiers=[
@@ -1,105 +0,0 @@
1
- import threading
2
- import time
3
- import e6data_python_connector.cluster_server.cluster_pb2 as cluster_pb2
4
- import e6data_python_connector.cluster_server.cluster_pb2_grpc as cluster_pb2_grpc
5
- import grpc
6
- from grpc._channel import _InactiveRpcError
7
- import multiprocessing
8
-
9
-
10
- class _StatusLock:
11
- _LOCK_TIMEOUT = 500
12
-
13
- def __init__(self):
14
- self._status_thread_lock = threading.Lock()
15
- self._status_multiprocessing_lock = multiprocessing.Semaphore()
16
- self._is_active = False
17
-
18
- @property
19
- def is_active(self) -> bool:
20
- return self._is_active
21
-
22
- def set_active(self):
23
- self._is_active = True
24
-
25
- def __enter__(self):
26
- self._status_thread_lock.acquire(timeout=self._LOCK_TIMEOUT)
27
- self._status_multiprocessing_lock.acquire(timeout=self._LOCK_TIMEOUT)
28
- return self
29
-
30
- def __exit__(self, exc_type, exc_val, exc_tb):
31
- self._status_thread_lock.release()
32
- self._status_multiprocessing_lock.release()
33
-
34
-
35
- status_lock = _StatusLock()
36
-
37
-
38
- class ClusterManager:
39
- def __init__(self, host: str, port: int, user: str, password: str, secure_channel: bool = False, timeout=60 * 3):
40
- self._host = host
41
- self._port = port
42
- self._user = user
43
- self._password = password
44
- self._timeout = time.time() + timeout
45
- self._secure_channel = secure_channel
46
-
47
- @property
48
- def _get_connection(self):
49
- if self._secure_channel:
50
- self._channel = grpc.secure_channel(
51
- target='{}:{}'.format(self._host, self._port),
52
- credentials=grpc.ssl_channel_credentials()
53
- )
54
- else:
55
- self._channel = grpc.insecure_channel(
56
- target='{}:{}'.format(self._host, self._port)
57
- )
58
- return cluster_pb2_grpc.ClusterServiceStub(self._channel)
59
-
60
- def resume(self) -> bool:
61
- """
62
- Return True if cluster came to active state else False.
63
- """
64
- with status_lock as lock:
65
- if lock.is_active:
66
- return True
67
- status_payload = cluster_pb2.ClusterStatusRequest(
68
- user=self._user,
69
- password=self._password
70
- )
71
- current_status = self._get_connection.status(status_payload)
72
- if current_status.status == 'suspended':
73
- payload = cluster_pb2.ResumeRequest(
74
- user=self._user,
75
- password=self._password
76
- )
77
- response = self._get_connection.resume(payload)
78
- elif current_status.status == 'active':
79
- return True
80
- elif current_status.status != 'resuming':
81
- """
82
- If cluster is in resuming state already, start watching for the status.
83
- Cluster is in different state, cannot resume.
84
- """
85
- return False
86
- while True:
87
- try:
88
- status_payload = cluster_pb2.ClusterStatusRequest(
89
- user=self._user,
90
- password=self._password
91
- )
92
- response = self._get_connection.status(status_payload)
93
- if response.status == 'active':
94
- lock.set_active()
95
- return True
96
- if response.status in ['suspended', 'failed']:
97
- return False
98
- if time.time() > self._timeout:
99
- return False
100
- except _InactiveRpcError as e:
101
- pass
102
- time.sleep(5)
103
-
104
- def suspend(self):
105
- pass
@@ -1,8 +0,0 @@
1
- sqlalchemy>=1.0.0
2
- future
3
- python-dateutil
4
- pycryptodome
5
- pytz
6
- thrift
7
- grpcio>=1.65.1
8
- grpcio-tools