e6data-python-connector 2.2.2.3rc2__tar.gz → 2.2.3__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 (36) hide show
  1. {e6data_python_connector-2.2.2.3rc2/e6data_python_connector.egg-info → e6data_python_connector-2.2.3}/PKG-INFO +2 -2
  2. {e6data_python_connector-2.2.2.3rc2 → e6data_python_connector-2.2.3}/README.md +1 -1
  3. {e6data_python_connector-2.2.2.3rc2 → e6data_python_connector-2.2.3}/e6data_python_connector/cluster_manager.py +46 -22
  4. {e6data_python_connector-2.2.2.3rc2 → e6data_python_connector-2.2.3}/e6data_python_connector/e6data_grpc.py +352 -48
  5. {e6data_python_connector-2.2.2.3rc2 → e6data_python_connector-2.2.3/e6data_python_connector.egg-info}/PKG-INFO +2 -2
  6. {e6data_python_connector-2.2.2.3rc2 → e6data_python_connector-2.2.3}/setup.py +1 -1
  7. {e6data_python_connector-2.2.2.3rc2 → e6data_python_connector-2.2.3}/LICENSE +0 -0
  8. {e6data_python_connector-2.2.2.3rc2 → e6data_python_connector-2.2.3}/MANIFEST.in +0 -0
  9. {e6data_python_connector-2.2.2.3rc2 → e6data_python_connector-2.2.3}/e6data_python_connector/__init__.py +0 -0
  10. {e6data_python_connector-2.2.2.3rc2 → e6data_python_connector-2.2.3}/e6data_python_connector/cluster_server/__init__.py +0 -0
  11. {e6data_python_connector-2.2.2.3rc2 → e6data_python_connector-2.2.3}/e6data_python_connector/cluster_server/cluster_pb2.py +0 -0
  12. {e6data_python_connector-2.2.2.3rc2 → e6data_python_connector-2.2.3}/e6data_python_connector/cluster_server/cluster_pb2_grpc.py +0 -0
  13. {e6data_python_connector-2.2.2.3rc2 → e6data_python_connector-2.2.3}/e6data_python_connector/common.py +0 -0
  14. {e6data_python_connector-2.2.2.3rc2 → e6data_python_connector-2.2.3}/e6data_python_connector/constants.py +0 -0
  15. {e6data_python_connector-2.2.2.3rc2 → e6data_python_connector-2.2.3}/e6data_python_connector/datainputstream.py +0 -0
  16. {e6data_python_connector-2.2.2.3rc2 → e6data_python_connector-2.2.3}/e6data_python_connector/date_time_utils.py +0 -0
  17. {e6data_python_connector-2.2.2.3rc2 → e6data_python_connector-2.2.3}/e6data_python_connector/dialect.py +0 -0
  18. {e6data_python_connector-2.2.2.3rc2 → e6data_python_connector-2.2.3}/e6data_python_connector/e6x_vector/__init__.py +0 -0
  19. {e6data_python_connector-2.2.2.3rc2 → e6data_python_connector-2.2.3}/e6data_python_connector/e6x_vector/constants.py +0 -0
  20. {e6data_python_connector-2.2.2.3rc2 → e6data_python_connector-2.2.3}/e6data_python_connector/e6x_vector/ttypes.py +0 -0
  21. {e6data_python_connector-2.2.2.3rc2 → e6data_python_connector-2.2.3}/e6data_python_connector/exceptions.py +0 -0
  22. {e6data_python_connector-2.2.2.3rc2 → e6data_python_connector-2.2.3}/e6data_python_connector/server/QueryEngineService.py +0 -0
  23. {e6data_python_connector-2.2.2.3rc2 → e6data_python_connector-2.2.3}/e6data_python_connector/server/__init__.py +0 -0
  24. {e6data_python_connector-2.2.2.3rc2 → e6data_python_connector-2.2.3}/e6data_python_connector/server/constants.py +0 -0
  25. {e6data_python_connector-2.2.2.3rc2 → e6data_python_connector-2.2.3}/e6data_python_connector/server/e6x_engine_pb2.py +0 -0
  26. {e6data_python_connector-2.2.2.3rc2 → e6data_python_connector-2.2.3}/e6data_python_connector/server/e6x_engine_pb2.pyi +0 -0
  27. {e6data_python_connector-2.2.2.3rc2 → e6data_python_connector-2.2.3}/e6data_python_connector/server/e6x_engine_pb2_grpc.py +0 -0
  28. {e6data_python_connector-2.2.2.3rc2 → e6data_python_connector-2.2.3}/e6data_python_connector/server/ttypes.py +0 -0
  29. {e6data_python_connector-2.2.2.3rc2 → e6data_python_connector-2.2.3}/e6data_python_connector/typeId.py +0 -0
  30. {e6data_python_connector-2.2.2.3rc2 → e6data_python_connector-2.2.3}/e6data_python_connector.egg-info/SOURCES.txt +0 -0
  31. {e6data_python_connector-2.2.2.3rc2 → e6data_python_connector-2.2.3}/e6data_python_connector.egg-info/dependency_links.txt +0 -0
  32. {e6data_python_connector-2.2.2.3rc2 → e6data_python_connector-2.2.3}/e6data_python_connector.egg-info/entry_points.txt +0 -0
  33. {e6data_python_connector-2.2.2.3rc2 → e6data_python_connector-2.2.3}/e6data_python_connector.egg-info/requires.txt +0 -0
  34. {e6data_python_connector-2.2.2.3rc2 → e6data_python_connector-2.2.3}/e6data_python_connector.egg-info/top_level.txt +0 -0
  35. {e6data_python_connector-2.2.2.3rc2 → e6data_python_connector-2.2.3}/pyproject.toml +0 -0
  36. {e6data_python_connector-2.2.2.3rc2 → e6data_python_connector-2.2.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: e6data-python-connector
3
- Version: 2.2.2.3rc2
3
+ Version: 2.2.3
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
- ![version](https://img.shields.io/badge/version-2.2.3.rc2-blue.svg)
41
+ ![version](https://img.shields.io/badge/version-2.2.3-blue.svg)
42
42
 
43
43
  ## Introduction
44
44
 
@@ -1,6 +1,6 @@
1
1
  # e6data Python Connector
2
2
 
3
- ![version](https://img.shields.io/badge/version-2.2.3.rc2-blue.svg)
3
+ ![version](https://img.shields.io/badge/version-2.2.3-blue.svg)
4
4
 
5
5
  ## Introduction
6
6
 
@@ -8,6 +8,19 @@ import multiprocessing
8
8
 
9
9
 
10
10
  def _get_grpc_header(engine_ip=None, cluster=None):
11
+ """
12
+ Generate gRPC metadata headers for the request.
13
+
14
+ This function creates a list of metadata headers to be used in gRPC requests.
15
+ It includes optional headers for the engine IP and cluster UUID.
16
+
17
+ Args:
18
+ engine_ip (str, optional): The IP address of the engine. Defaults to None.
19
+ cluster (str, optional): The UUID of the cluster. Defaults to None.
20
+
21
+ Returns:
22
+ list: A list of tuples representing the gRPC metadata headers.
23
+ """
11
24
  metadata = []
12
25
  if engine_ip:
13
26
  metadata.append(('plannerip', engine_ip))
@@ -123,7 +136,7 @@ class ClusterManager:
123
136
  cluster_uuid (str): The unique identifier for the target cluster.
124
137
  """
125
138
 
126
- def __init__(self, host: str, port: int, user: str, password: str, secure_channel: bool = False, timeout=60 * 3, cluster_uuid=None):
139
+ def __init__(self, host: str, port: int, user: str, password: str, secure_channel: bool = False, timeout=60 * 3, cluster_uuid=None, grpc_options=None):
127
140
  """
128
141
  Initializes a new instance of the ClusterManager class.
129
142
 
@@ -147,6 +160,9 @@ class ClusterManager:
147
160
  self._timeout = time.time() + timeout
148
161
  self._secure_channel = secure_channel
149
162
  self.cluster_uuid = cluster_uuid
163
+ self._grpc_options = grpc_options
164
+ if grpc_options is None:
165
+ self._grpc_options = dict()
150
166
 
151
167
  @property
152
168
  def _get_connection(self):
@@ -161,14 +177,34 @@ class ClusterManager:
161
177
  if self._secure_channel:
162
178
  self._channel = grpc.secure_channel(
163
179
  target='{}:{}'.format(self._host, self._port),
180
+ options=self._grpc_options,
164
181
  credentials=grpc.ssl_channel_credentials()
165
182
  )
166
183
  else:
167
184
  self._channel = grpc.insecure_channel(
168
- target='{}:{}'.format(self._host, self._port)
185
+ target='{}:{}'.format(self._host, self._port),
186
+ options=self._grpc_options
169
187
  )
170
188
  return cluster_pb2_grpc.ClusterServiceStub(self._channel)
171
189
 
190
+ def _check_cluster_status(self):
191
+ while True:
192
+ try:
193
+ # Create a status request payload with user credentials
194
+ status_payload = cluster_pb2.ClusterStatusRequest(
195
+ user=self._user,
196
+ password=self._password
197
+ )
198
+ # Send the status request to the cluster service
199
+ response = self._get_connection.status(
200
+ status_payload,
201
+ metadata=_get_grpc_header(cluster=self.cluster_uuid)
202
+ )
203
+ # Yield the current status
204
+ yield response.status
205
+ except _InactiveRpcError as e:
206
+ yield None
207
+
172
208
  def resume(self) -> bool:
173
209
  """
174
210
  Resumes the cluster if it is currently suspended or not in the 'active' state.
@@ -229,27 +265,15 @@ class ClusterManager:
229
265
  """
230
266
  return False
231
267
 
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
268
+ for status in self._check_cluster_status():
269
+ if status == 'active':
270
+ lock.set_active()
271
+ return True
272
+ elif status == 'failed' or time.time() > self._timeout:
273
+ return False
274
+ # Wait for 5 seconds before the next status check
252
275
  time.sleep(5)
276
+ return False
253
277
 
254
278
  def suspend(self):
255
279
  """
@@ -80,8 +80,9 @@ def re_auth(func):
80
80
  raise e
81
81
  if e.code() == grpc.StatusCode.INTERNAL and 'Access denied' in e.details():
82
82
  time.sleep(0.2)
83
- print(f'RE_AUTH: Function Name: {func}')
84
- print(f'RE_AUTH: Error Found {e}')
83
+ _logger.info(f'RE_AUTH: Function Name: {func}')
84
+ _logger.info(f'RE_AUTH: Error Found {e}')
85
+ self.connection.get_re_authenticate_session_id()
85
86
  else:
86
87
  raise e
87
88
 
@@ -173,6 +174,7 @@ class Connection(object):
173
174
  - max_receive_message_length: This parameter sets the maximum allowed size (in bytes) for incoming messages on the gRPC server.
174
175
  - 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
175
176
  - grpc_prepare_timeout: Timeout for prepare statement API call (default to 10 minutes).
177
+ - keepalive_time_ms: This parameter defines the time, in milliseconds, Default to 30 seconds
176
178
  """
177
179
  if not username or not password:
178
180
  raise ValueError("username or password cannot be empty.")
@@ -192,43 +194,88 @@ class Connection(object):
192
194
 
193
195
  self._auto_resume = auto_resume
194
196
 
195
- self._keepalive_timeout_ms = 900000
196
- self._max_receive_message_length = -1
197
- self._max_send_message_length = 300 * 1024 * 1024 # mb
198
- self.grpc_prepare_timeout = 10 * 60 # 10 minutes
199
-
200
- if isinstance(grpc_options, dict):
201
- self._keepalive_timeout_ms = grpc_options.get('keepalive_timeout_ms') or self._keepalive_timeout_ms
202
- self._max_receive_message_length = grpc_options.get(
203
- 'max_receive_message_length') or self._max_receive_message_length
204
- self._max_send_message_length = grpc_options.get('max_send_message_length') or self._max_send_message_length
205
- self.grpc_prepare_timeout = grpc_options.get('grpc_prepare_timeout') or self.grpc_prepare_timeout
197
+ self._grpc_options = grpc_options
198
+ if self._grpc_options is None:
199
+ self._grpc_options = dict()
200
+ self.grpc_prepare_timeout = self._grpc_options.get('grpc_prepare_timeout') or 10 * 60 # 10 minutes
206
201
  self._create_client()
207
202
 
203
+ @property
204
+ def _get_grpc_options(self):
205
+ """
206
+ Property to get gRPC options for the connection.
207
+
208
+ This method checks if the gRPC options are already cached. If not, it creates a copy of the
209
+ provided gRPC options and merges them with the default options. The merged options are then
210
+ cached for future use.
211
+
212
+ Returns:
213
+ list: A list of tuples containing gRPC options.
214
+ """
215
+ if not hasattr(self, '_cached_grpc_options'):
216
+ grpc_options = self._grpc_options.copy()
217
+ default_options = {
218
+ "keepalive_timeout_ms": 900000, # Time in milliseconds to keep the connection alive.
219
+ "max_receive_message_length": -1, # Maximum size of received messages.
220
+ "max_send_message_length": 300 * 1024 * 1024, # Maximum size of sent messages (300 MB).
221
+ "grpc_prepare_timeout": self.grpc_prepare_timeout, # Timeout for prepare statement API call.
222
+ "keepalive_time_ms": 30000, # Time in milliseconds between keep-alive pings.
223
+ "keepalive_permit_without_calls": 1, # Allow keep-alives with no active RPCs.
224
+ "http2.max_pings_without_data": 0, # Unlimited pings without data.
225
+ "http2.min_time_between_pings_ms": 15000, # Minimum time between pings (15 seconds).
226
+ "http2.min_ping_interval_without_data_ms": 15000, # Minimum interval between pings without data (15 seconds).
227
+ }
228
+ if grpc_options:
229
+ for key, value in grpc_options.items():
230
+ default_options[key] = value
231
+
232
+ self._cached_grpc_options = [(f'grpc.{key}', value) for key, value in default_options.items()]
233
+
234
+ return self._cached_grpc_options
235
+
208
236
  def _create_client(self):
237
+ """
238
+ Creates a gRPC client for the connection.
239
+
240
+ This method initializes a gRPC channel based on whether a secure channel is required or not.
241
+ It then creates a client stub for the QueryEngineService.
242
+
243
+ If the secure channel is enabled, it uses `grpc.secure_channel` with SSL credentials.
244
+ Otherwise, it uses `grpc.insecure_channel`.
245
+
246
+ The gRPC options are retrieved from the `_get_grpc_options` property.
247
+
248
+ Raises:
249
+ grpc.RpcError: If there is an error in creating the gRPC channel or client stub.
250
+ """
209
251
  if self._secure_channel:
210
252
  self._channel = grpc.secure_channel(
211
253
  target='{}:{}'.format(self._host, self._port),
212
- options=[
213
- ("grpc.keepalive_timeout_ms", self._keepalive_timeout_ms),
214
- ('grpc.max_send_message_length', self._max_send_message_length),
215
- ('grpc.max_receive_message_length', self._max_receive_message_length)
216
- ],
254
+ options=self._get_grpc_options,
217
255
  credentials=grpc.ssl_channel_credentials()
218
256
  )
219
257
  else:
220
258
  self._channel = grpc.insecure_channel(
221
259
  target='{}:{}'.format(self._host, self._port),
222
- options=[
223
- ("grpc.keepalive_timeout_ms", self._keepalive_timeout_ms),
224
- ('grpc.max_send_message_length', self._max_send_message_length),
225
- ('grpc.max_receive_message_length', self._max_receive_message_length)
226
- ]
260
+ options=self._get_grpc_options
227
261
  )
228
262
  self._client = e6x_engine_pb2_grpc.QueryEngineServiceStub(self._channel)
229
263
 
230
264
  def get_re_authenticate_session_id(self):
231
- self._session_id = None
265
+ """
266
+ Re-authenticates the session by closing the current connection and creating a new client.
267
+
268
+ This method is used to re-establish the session ID by closing the existing gRPC channel,
269
+ creating a new client, and then retrieving a new session ID.
270
+
271
+ Returns:
272
+ str: The new session ID after re-authentication.
273
+
274
+ Raises:
275
+ Exception: If there is an error during the re-authentication process.
276
+ """
277
+ self.close()
278
+ self._create_client()
232
279
  return self.get_session_id
233
280
 
234
281
  @property
@@ -281,35 +328,58 @@ class Connection(object):
281
328
  raise e
282
329
  return self._session_id
283
330
 
284
- def update_users(self, user_info):
285
- self.client.updateUsers(userInfo=user_info)
286
-
287
- def set_prop_map(self, prop_map: str):
288
- """
289
- To enable to disable the caches.
290
- :param prop_map: To set engine props
331
+ def __enter__(self):
291
332
  """
292
- set_props_request = e6x_engine_pb2.SetPropsRequest(sessionId=self.get_session_id, props=prop_map)
293
- self._client.setProps(set_props_request)
333
+ Enters the runtime context related to this object.
294
334
 
295
- def __enter__(self):
296
- """Transport should already be opened by __init__"""
335
+ This method is called when the execution flow enters the context of the `with` statement.
336
+
337
+ Returns:
338
+ Connection: The current instance of the connection.
339
+ """
297
340
  return self
298
341
 
299
342
  def __exit__(self, exc_type, exc_val, exc_tb):
300
- """Call close"""
343
+ """
344
+ Exits the runtime context related to this object.
345
+
346
+ This method is called when the execution flow exits the context of the `with` statement.
347
+
348
+ Args:
349
+ exc_type (Type[BaseException]): The type of exception raised (if any).
350
+ exc_val (BaseException): The exception instance raised (if any).
351
+ exc_tb (Traceback): The traceback object of the exception (if any).
352
+ """
301
353
  self.close()
302
354
 
303
355
  def close(self):
356
+ """
357
+ Closes the gRPC channel and resets the session ID.
358
+
359
+ This method ensures that the gRPC channel is properly closed and the session ID is reset to None.
360
+ """
304
361
  if self._channel is not None:
305
362
  self._channel.close()
306
363
  self._channel = None
307
364
  self._session_id = None
308
365
 
309
366
  def check_connection(self):
367
+ """
368
+ Checks if the gRPC channel is still open.
369
+
370
+ Returns:
371
+ bool: True if the gRPC channel is open, False otherwise.
372
+ """
310
373
  return self._channel is not None
311
374
 
312
375
  def clear(self, query_id, engine_ip=None):
376
+ """
377
+ Clears the query results from the server.
378
+
379
+ Args:
380
+ query_id (str): The ID of the query to be cleared.
381
+ engine_ip (str, optional): The IP address of the engine. Defaults to None.
382
+ """
313
383
  clear_request = e6x_engine_pb2.ClearRequest(
314
384
  sessionId=self.get_session_id,
315
385
  queryId=query_id,
@@ -321,10 +391,22 @@ class Connection(object):
321
391
  )
322
392
 
323
393
  def reopen(self):
394
+ """
395
+ Reopens the gRPC channel by closing the current channel and creating a new client.
396
+
397
+ This method is useful for re-establishing the connection if it was previously closed.
398
+ """
324
399
  self._channel.close()
325
400
  self._create_client()
326
401
 
327
402
  def query_cancel(self, engine_ip, query_id):
403
+ """
404
+ Cancels the execution of a query on the server.
405
+
406
+ Args:
407
+ engine_ip (str): The IP address of the engine.
408
+ query_id (str): The ID of the query to be canceled.
409
+ """
328
410
  cancel_query_request = e6x_engine_pb2.CancelQueryRequest(
329
411
  engineIP=engine_ip,
330
412
  sessionId=self.get_session_id,
@@ -336,6 +418,15 @@ class Connection(object):
336
418
  )
337
419
 
338
420
  def dry_run(self, query):
421
+ """
422
+ Performs a dry run of the query to validate its syntax and structure.
423
+
424
+ Args:
425
+ query (str): The SQL query to be validated.
426
+
427
+ Returns:
428
+ str: The result of the dry run validation.
429
+ """
339
430
  dry_run_request = e6x_engine_pb2.DryRunRequest(
340
431
  sessionId=self.get_session_id,
341
432
  schema=self.database,
@@ -348,6 +439,16 @@ class Connection(object):
348
439
  return dry_run_response.dryrunValue
349
440
 
350
441
  def get_tables(self, catalog, database):
442
+ """
443
+ Retrieves the list of tables from the specified catalog and database.
444
+
445
+ Args:
446
+ catalog (str): The catalog name.
447
+ database (str): The database name.
448
+
449
+ Returns:
450
+ list: A list of table names.
451
+ """
351
452
  get_table_request = e6x_engine_pb2.GetTablesV2Request(
352
453
  sessionId=self.get_session_id,
353
454
  schema=database,
@@ -360,6 +461,17 @@ class Connection(object):
360
461
  return list(get_table_response.tables)
361
462
 
362
463
  def get_columns(self, catalog, database, table):
464
+ """
465
+ Retrieves the list of columns for the specified table in the given catalog and database.
466
+
467
+ Args:
468
+ catalog (str): The catalog name.
469
+ database (str): The database name.
470
+ table (str): The table name.
471
+
472
+ Returns:
473
+ list: A list of dictionaries containing column information.
474
+ """
363
475
  get_columns_request = e6x_engine_pb2.GetColumnsV2Request(
364
476
  sessionId=self.get_session_id,
365
477
  schema=database,
@@ -373,6 +485,15 @@ class Connection(object):
373
485
  return [{'fieldName': row.fieldName, 'fieldType': row.fieldType} for row in get_columns_response.fieldInfo]
374
486
 
375
487
  def get_schema_names(self, catalog):
488
+ """
489
+ Retrieves the list of schema names from the specified catalog.
490
+
491
+ Args:
492
+ catalog (str): The catalog name.
493
+
494
+ Returns:
495
+ list: A list of schema names.
496
+ """
376
497
  get_schema_request = e6x_engine_pb2.GetSchemaNamesV2Request(
377
498
  sessionId=self.get_session_id,
378
499
  catalog=catalog
@@ -384,29 +505,65 @@ class Connection(object):
384
505
  return list(get_schema_response.schemas)
385
506
 
386
507
  def commit(self):
387
- """We do not support transactions, so this does nothing."""
508
+ """
509
+ Commits the current transaction.
510
+
511
+ Note:
512
+ This method does nothing as transactions are not supported.
513
+ """
388
514
  pass
389
515
 
390
516
  def cursor(self, catalog_name=None, db_name=None):
391
- """Return a new :py:class:`Cursor` object using the connection."""
517
+ """
518
+ Creates a new cursor object for executing queries.
519
+
520
+ Args:
521
+ catalog_name (str, optional): The catalog name. Defaults to None.
522
+ db_name (str, optional): The database name. Defaults to None.
523
+
524
+ Returns:
525
+ Cursor: A new cursor object.
526
+ """
392
527
  return Cursor(self, database=db_name, catalog_name=catalog_name)
393
528
 
394
529
  def rollback(self):
395
- raise Exception("e6xdb does not support transactions") # pragma: no cover
530
+ """
531
+ Rolls back the current transaction.
532
+
533
+ Raises:
534
+ Exception: Always raises an exception as transactions are not supported.
535
+ """
536
+ raise Exception("e6data does not support transactions") # pragma: no cover
396
537
 
397
538
  @property
398
539
  def client(self):
540
+ """
541
+ Returns the gRPC client stub for interacting with the server.
542
+
543
+ Returns:
544
+ e6x_engine_pb2_grpc.QueryEngineServiceStub: The gRPC client stub.
545
+ """
399
546
  return self._client
400
547
 
401
548
 
402
549
  class Cursor(DBAPICursor):
403
- """These objects represent a database cursor, which is used to manage the context of a fetch
550
+ """
551
+ These objects represent a database cursor, which is used to manage the context of a fetch
404
552
  operation.
405
553
  Cursors are not isolated, i.e., any changes done to the database by a cursor are immediately
406
554
  visible by other cursors or connections.
407
555
  """
408
556
 
409
557
  def __init__(self, connection: Connection, array_size=1000, database=None, catalog_name=None):
558
+ """
559
+ Initialize a new Cursor object.
560
+
561
+ Args:
562
+ connection (Connection): The connection object to the database.
563
+ array_size (int, optional): The number of rows to fetch at a time. Defaults to 1000.
564
+ database (str, optional): The database name. Defaults to None.
565
+ catalog_name (str, optional): The catalog name. Defaults to None.
566
+ """
410
567
  super(Cursor, self).__init__()
411
568
  self._array_size = array_size
412
569
  self.connection = connection
@@ -427,15 +584,32 @@ class Cursor(DBAPICursor):
427
584
 
428
585
  @property
429
586
  def metadata(self):
587
+ """
588
+ Get the gRPC metadata for the current query.
589
+
590
+ Returns:
591
+ list: A list of tuples containing gRPC metadata.
592
+ """
430
593
  return _get_grpc_header(engine_ip=self._engine_ip, cluster=self.connection.cluster_uuid)
431
594
 
432
595
  @property
433
596
  def arraysize(self):
597
+ """
598
+ Get the array size for fetching rows.
599
+
600
+ Returns:
601
+ int: The number of rows to fetch at a time.
602
+ """
434
603
  return self._arraysize
435
604
 
436
605
  @arraysize.setter
437
606
  def arraysize(self, value):
438
- """Array size cannot be None, and should be an integer"""
607
+ """
608
+ Set the array size for fetching rows.
609
+
610
+ Args:
611
+ value (int): The number of rows to fetch at a time.
612
+ """
439
613
  default_arraysize = 1000
440
614
  try:
441
615
  self._arraysize = int(value) or default_arraysize
@@ -471,14 +645,29 @@ class Cursor(DBAPICursor):
471
645
  return self._description
472
646
 
473
647
  def __enter__(self):
648
+ """
649
+ Enter the runtime context related to this object.
650
+
651
+ Returns:
652
+ Cursor: The current instance of the cursor.
653
+ """
474
654
  return self
475
655
 
476
656
  def __exit__(self, exc_type, exc_val, exc_tb):
657
+ """
658
+ Exit the runtime context related to this object.
659
+
660
+ Args:
661
+ exc_type (Type[BaseException]): The type of exception raised (if any).
662
+ exc_val (BaseException): The exception instance raised (if any).
663
+ exc_tb (Traceback): The traceback object of the exception (if any).
664
+ """
477
665
  self.close()
478
666
 
479
667
  def close(self):
480
- """Close the operation handle"""
481
- # self.connection.close()
668
+ """
669
+ Close the operation handle and reset the cursor state.
670
+ """
482
671
  try:
483
672
  self.clear()
484
673
  except:
@@ -495,17 +684,44 @@ class Cursor(DBAPICursor):
495
684
  self._database = None
496
685
 
497
686
  def get_tables(self):
687
+ """
688
+ Retrieve the list of tables from the current database.
689
+
690
+ Returns:
691
+ list: A list of table names.
692
+ """
498
693
  schema = self.connection.database
499
694
  return self.connection.get_tables(catalog=self._catalog_name, database=schema)
500
695
 
501
696
  def get_columns(self, table):
697
+ """
698
+ Retrieve the list of columns for the specified table.
699
+
700
+ Args:
701
+ table (str): The table name.
702
+
703
+ Returns:
704
+ list: A list of dictionaries containing column information.
705
+ """
502
706
  schema = self.connection.database
503
707
  return self.connection.get_columns(catalog=self._catalog_name, database=schema, table=table)
504
708
 
505
709
  def get_schema_names(self):
710
+ """
711
+ Retrieve the list of schema names from the current catalog.
712
+
713
+ Returns:
714
+ list: A list of schema names.
715
+ """
506
716
  return self.connection.get_schema_names(catalog=self._catalog_name)
507
717
 
508
718
  def clear(self, query_id=None):
719
+ """
720
+ Clear the query results from the server.
721
+
722
+ Args:
723
+ query_id (str, optional): The ID of the query to be cleared. Defaults to None.
724
+ """
509
725
  if not query_id:
510
726
  query_id = self._query_id
511
727
  clear_request = e6x_engine_pb2.ClearOrCancelQueryRequest(
@@ -516,9 +732,24 @@ class Cursor(DBAPICursor):
516
732
  return self.connection.client.clearOrCancelQuery(clear_request, metadata=self.metadata)
517
733
 
518
734
  def cancel(self, query_id):
735
+ """
736
+ Cancel the execution of a query on the server.
737
+
738
+ Args:
739
+ query_id (str): The ID of the query to be canceled.
740
+ """
519
741
  self.connection.query_cancel(engine_ip=self._engine_ip, query_id=query_id)
520
742
 
521
743
  def status(self, query_id):
744
+ """
745
+ Get the status of the specified query.
746
+
747
+ Args:
748
+ query_id (str): The ID of the query.
749
+
750
+ Returns:
751
+ StatusResponse: The status response of the query.
752
+ """
522
753
  status_request = e6x_engine_pb2.StatusRequest(
523
754
  sessionId=self.connection.get_session_id,
524
755
  queryId=query_id,
@@ -528,12 +759,17 @@ class Cursor(DBAPICursor):
528
759
 
529
760
  @re_auth
530
761
  def execute(self, operation, parameters=None, **kwargs):
531
- """Prepare and execute a database operation (query or command).
532
- Return values are not defined.
533
- """
534
762
  """
535
- Semicolon is now not supported. So removing it from query end.
763
+ Prepare and execute a database operation (query or command).
764
+
765
+ Args:
766
+ operation (str): The SQL query or command to execute.
767
+ parameters (dict, optional): The parameters to bind to the query. Defaults to None.
768
+
769
+ Returns:
770
+ str: The query ID of the executed query.
536
771
  """
772
+ # Semicolon is now not supported. So removing it from query end.
537
773
  operation = operation.strip() # Remove leading and trailing whitespaces.
538
774
  if operation.endswith(';'):
539
775
  operation = operation[:-1]
@@ -597,10 +833,19 @@ class Cursor(DBAPICursor):
597
833
 
598
834
  @property
599
835
  def rowcount(self):
836
+ """
837
+ Get the number of rows affected by the last execute operation.
838
+
839
+ Returns:
840
+ int: The number of rows affected.
841
+ """
600
842
  self.update_mete_data()
601
843
  return self._rowcount
602
844
 
603
845
  def update_mete_data(self):
846
+ """
847
+ Update the metadata for the current query.
848
+ """
604
849
  result_meta_data_request = e6x_engine_pb2.GetResultMetadataRequest(
605
850
  engineIP=self._engine_ip,
606
851
  sessionId=self.connection.get_session_id,
@@ -615,6 +860,12 @@ class Cursor(DBAPICursor):
615
860
  self._is_metadata_updated = True
616
861
 
617
862
  def _fetch_more(self):
863
+ """
864
+ Fetch more rows from the server.
865
+
866
+ Returns:
867
+ list: A list of rows fetched from the server.
868
+ """
618
869
  batch_size = self._arraysize
619
870
  self._data = list()
620
871
  for i in range(batch_size):
@@ -625,6 +876,12 @@ class Cursor(DBAPICursor):
625
876
  return self._data
626
877
 
627
878
  def _fetch_all(self):
879
+ """
880
+ Fetch all rows from the server.
881
+
882
+ Returns:
883
+ list: A list of all rows fetched from the server.
884
+ """
628
885
  self._data = list()
629
886
  while True:
630
887
  rows = self.fetch_batch()
@@ -636,6 +893,15 @@ class Cursor(DBAPICursor):
636
893
  return rows
637
894
 
638
895
  def fetchall_buffer(self, query_id=None):
896
+ """
897
+ Fetch all rows from the server in a buffered manner.
898
+
899
+ Args:
900
+ query_id (str, optional): The ID of the query. Defaults to None.
901
+
902
+ Yields:
903
+ list: A list of rows fetched from the server.
904
+ """
639
905
  if query_id:
640
906
  self._query_id = query_id
641
907
  while True:
@@ -645,6 +911,12 @@ class Cursor(DBAPICursor):
645
911
  yield rows
646
912
 
647
913
  def fetch_batch(self):
914
+ """
915
+ Fetch a batch of rows from the server.
916
+
917
+ Returns:
918
+ list: A list of rows fetched from the server.
919
+ """
648
920
  client = self.connection.client
649
921
  get_next_result_batch_request = e6x_engine_pb2.GetNextResultBatchRequest(
650
922
  engineIP=self._engine_ip,
@@ -664,9 +936,24 @@ class Cursor(DBAPICursor):
664
936
  return read_rows_from_chunk(self._query_columns_description, buffer)
665
937
 
666
938
  def fetchall(self):
939
+ """
940
+ Fetch all rows from the server.
941
+
942
+ Returns:
943
+ list: A list of all rows fetched from the server.
944
+ """
667
945
  return self._fetch_all()
668
946
 
669
947
  def fetchmany(self, size: int = None):
948
+ """
949
+ Fetch a specified number of rows from the server.
950
+
951
+ Args:
952
+ size (int, optional): The number of rows to fetch. Defaults to None.
953
+
954
+ Returns:
955
+ list: A list of rows fetched from the server.
956
+ """
670
957
  if size is None:
671
958
  size = self.arraysize
672
959
  if self._data is None:
@@ -685,13 +972,24 @@ class Cursor(DBAPICursor):
685
972
  return rows
686
973
 
687
974
  def fetchone(self):
688
- # _logger.info("fetch One returning the batch itself which is limited by predefined no.of rows")
975
+ """
976
+ Fetch a single row from the server.
977
+
978
+ Returns:
979
+ list: A single row fetched from the server.
980
+ """
689
981
  rows = self.fetchmany(1)
690
982
  if rows is None or len(rows) == 0:
691
983
  return None
692
984
  return rows
693
985
 
694
986
  def explain(self):
987
+ """
988
+ Get the execution plan for the current query.
989
+
990
+ Returns:
991
+ str: The execution plan of the query.
992
+ """
695
993
  explain_request = e6x_engine_pb2.ExplainRequest(
696
994
  engineIP=self._engine_ip,
697
995
  sessionId=self.connection.get_session_id,
@@ -704,6 +1002,12 @@ class Cursor(DBAPICursor):
704
1002
  return explain_response.explain
705
1003
 
706
1004
  def explain_analyse(self):
1005
+ """
1006
+ Get the execution plan for the current query.
1007
+
1008
+ Returns:
1009
+ dict: The execution plan of the query.
1010
+ """
707
1011
  explain_analyze_request = e6x_engine_pb2.ExplainAnalyzeRequest(
708
1012
  engineIP=self._engine_ip,
709
1013
  sessionId=self.connection.get_session_id,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: e6data-python-connector
3
- Version: 2.2.2.3rc2
3
+ Version: 2.2.3
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
- ![version](https://img.shields.io/badge/version-2.2.3.rc2-blue.svg)
41
+ ![version](https://img.shields.io/badge/version-2.2.3-blue.svg)
42
42
 
43
43
  ## Introduction
44
44
 
@@ -12,7 +12,7 @@
12
12
 
13
13
  import setuptools
14
14
 
15
- VERSION = (2, 2, 2, '3.rc2')
15
+ VERSION = (2, 2, 3)
16
16
 
17
17
 
18
18
  def get_long_desc():