e6data-python-connector 2.1.21__tar.gz → 2.2.0__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.1.21/e6data_python_connector.egg-info → e6data_python_connector-2.2.0}/PKG-INFO +2 -2
  2. {e6data_python_connector-2.1.21 → e6data_python_connector-2.2.0}/README.md +1 -1
  3. e6data_python_connector-2.2.0/e6data_python_connector/cluster_manager.py +105 -0
  4. e6data_python_connector-2.2.0/e6data_python_connector/cluster_server/__init__.py +0 -0
  5. e6data_python_connector-2.2.0/e6data_python_connector/cluster_server/cluster_pb2.py +38 -0
  6. e6data_python_connector-2.2.0/e6data_python_connector/cluster_server/cluster_pb2_grpc.py +132 -0
  7. {e6data_python_connector-2.1.21 → e6data_python_connector-2.2.0}/e6data_python_connector/dialect.py +4 -1
  8. {e6data_python_connector-2.1.21 → e6data_python_connector-2.2.0}/e6data_python_connector/e6data_grpc.py +33 -0
  9. {e6data_python_connector-2.1.21 → e6data_python_connector-2.2.0/e6data_python_connector.egg-info}/PKG-INFO +2 -2
  10. {e6data_python_connector-2.1.21 → e6data_python_connector-2.2.0}/e6data_python_connector.egg-info/SOURCES.txt +4 -0
  11. {e6data_python_connector-2.1.21 → e6data_python_connector-2.2.0}/setup.py +1 -1
  12. {e6data_python_connector-2.1.21 → e6data_python_connector-2.2.0}/LICENSE +0 -0
  13. {e6data_python_connector-2.1.21 → e6data_python_connector-2.2.0}/MANIFEST.in +0 -0
  14. {e6data_python_connector-2.1.21 → e6data_python_connector-2.2.0}/e6data_python_connector/__init__.py +0 -0
  15. {e6data_python_connector-2.1.21 → e6data_python_connector-2.2.0}/e6data_python_connector/common.py +0 -0
  16. {e6data_python_connector-2.1.21 → e6data_python_connector-2.2.0}/e6data_python_connector/constants.py +0 -0
  17. {e6data_python_connector-2.1.21 → e6data_python_connector-2.2.0}/e6data_python_connector/datainputstream.py +0 -0
  18. {e6data_python_connector-2.1.21 → e6data_python_connector-2.2.0}/e6data_python_connector/date_time_utils.py +0 -0
  19. {e6data_python_connector-2.1.21 → e6data_python_connector-2.2.0}/e6data_python_connector/e6x_vector/__init__.py +0 -0
  20. {e6data_python_connector-2.1.21 → e6data_python_connector-2.2.0}/e6data_python_connector/e6x_vector/constants.py +0 -0
  21. {e6data_python_connector-2.1.21 → e6data_python_connector-2.2.0}/e6data_python_connector/e6x_vector/ttypes.py +0 -0
  22. {e6data_python_connector-2.1.21 → e6data_python_connector-2.2.0}/e6data_python_connector/exceptions.py +0 -0
  23. {e6data_python_connector-2.1.21 → e6data_python_connector-2.2.0}/e6data_python_connector/server/QueryEngineService.py +0 -0
  24. {e6data_python_connector-2.1.21 → e6data_python_connector-2.2.0}/e6data_python_connector/server/__init__.py +0 -0
  25. {e6data_python_connector-2.1.21 → e6data_python_connector-2.2.0}/e6data_python_connector/server/constants.py +0 -0
  26. {e6data_python_connector-2.1.21 → e6data_python_connector-2.2.0}/e6data_python_connector/server/e6x_engine_pb2.py +0 -0
  27. {e6data_python_connector-2.1.21 → e6data_python_connector-2.2.0}/e6data_python_connector/server/e6x_engine_pb2.pyi +0 -0
  28. {e6data_python_connector-2.1.21 → e6data_python_connector-2.2.0}/e6data_python_connector/server/e6x_engine_pb2_grpc.py +0 -0
  29. {e6data_python_connector-2.1.21 → e6data_python_connector-2.2.0}/e6data_python_connector/server/ttypes.py +0 -0
  30. {e6data_python_connector-2.1.21 → e6data_python_connector-2.2.0}/e6data_python_connector/typeId.py +0 -0
  31. {e6data_python_connector-2.1.21 → e6data_python_connector-2.2.0}/e6data_python_connector.egg-info/dependency_links.txt +0 -0
  32. {e6data_python_connector-2.1.21 → e6data_python_connector-2.2.0}/e6data_python_connector.egg-info/entry_points.txt +0 -0
  33. {e6data_python_connector-2.1.21 → e6data_python_connector-2.2.0}/e6data_python_connector.egg-info/requires.txt +0 -0
  34. {e6data_python_connector-2.1.21 → e6data_python_connector-2.2.0}/e6data_python_connector.egg-info/top_level.txt +0 -0
  35. {e6data_python_connector-2.1.21 → e6data_python_connector-2.2.0}/pyproject.toml +0 -0
  36. {e6data_python_connector-2.1.21 → e6data_python_connector-2.2.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: e6data-python-connector
3
- Version: 2.1.21
3
+ Version: 2.2.0
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: Uniphi, Inc.
@@ -27,7 +27,7 @@ Requires-Dist: sqlalchemy
27
27
 
28
28
  # e6data Python Connector
29
29
 
30
- ![version](https://img.shields.io/badge/version-2.1.21-blue.svg)
30
+ ![version](https://img.shields.io/badge/version-2.2.0-blue.svg)
31
31
 
32
32
  ## Introduction
33
33
 
@@ -1,6 +1,6 @@
1
1
  # e6data Python Connector
2
2
 
3
- ![version](https://img.shields.io/badge/version-2.1.21-blue.svg)
3
+ ![version](https://img.shields.io/badge/version-2.2.0-blue.svg)
4
4
 
5
5
  ## Introduction
6
6
 
@@ -0,0 +1,105 @@
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
@@ -0,0 +1,38 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # source: cluster.proto
4
+ """Generated protocol buffer code."""
5
+ from google.protobuf import descriptor as _descriptor
6
+ from google.protobuf import descriptor_pool as _descriptor_pool
7
+ from google.protobuf import symbol_database as _symbol_database
8
+ from google.protobuf.internal import builder as _builder
9
+ # @@protoc_insertion_point(imports)
10
+
11
+ _sym_db = _symbol_database.Default()
12
+
13
+
14
+
15
+
16
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\rcluster.proto\"/\n\rResumeRequest\x12\x0c\n\x04user\x18\x01 \x01(\t\x12\x10\n\x08password\x18\x02 \x01(\t\" \n\x0eResumeResponse\x12\x0e\n\x06status\x18\x01 \x01(\t\"0\n\x0eSuspendRequest\x12\x0c\n\x04user\x18\x01 \x01(\t\x12\x10\n\x08password\x18\x02 \x01(\t\"!\n\x0fSuspendResponse\x12\x0e\n\x06status\x18\x01 \x01(\t\"6\n\x14\x43lusterStatusRequest\x12\x0c\n\x04user\x18\x01 \x01(\t\x12\x10\n\x08password\x18\x02 \x01(\t\"\'\n\x15\x43lusterStatusResponse\x12\x0e\n\x06status\x18\x01 \x01(\t2\xa2\x01\n\x0e\x43lusterService\x12)\n\x06resume\x12\x0e.ResumeRequest\x1a\x0f.ResumeResponse\x12,\n\x07suspend\x12\x0f.SuspendRequest\x1a\x10.SuspendResponse\x12\x37\n\x06status\x12\x15.ClusterStatusRequest\x1a\x16.ClusterStatusResponseB\x02P\x01\x62\x06proto3')
17
+
18
+ _globals = globals()
19
+ _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
20
+ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cluster_pb2', _globals)
21
+ if _descriptor._USE_C_DESCRIPTORS == False:
22
+ DESCRIPTOR._options = None
23
+ DESCRIPTOR._serialized_options = b'P\001'
24
+ _globals['_RESUMEREQUEST']._serialized_start=17
25
+ _globals['_RESUMEREQUEST']._serialized_end=64
26
+ _globals['_RESUMERESPONSE']._serialized_start=66
27
+ _globals['_RESUMERESPONSE']._serialized_end=98
28
+ _globals['_SUSPENDREQUEST']._serialized_start=100
29
+ _globals['_SUSPENDREQUEST']._serialized_end=148
30
+ _globals['_SUSPENDRESPONSE']._serialized_start=150
31
+ _globals['_SUSPENDRESPONSE']._serialized_end=183
32
+ _globals['_CLUSTERSTATUSREQUEST']._serialized_start=185
33
+ _globals['_CLUSTERSTATUSREQUEST']._serialized_end=239
34
+ _globals['_CLUSTERSTATUSRESPONSE']._serialized_start=241
35
+ _globals['_CLUSTERSTATUSRESPONSE']._serialized_end=280
36
+ _globals['_CLUSTERSERVICE']._serialized_start=283
37
+ _globals['_CLUSTERSERVICE']._serialized_end=445
38
+ # @@protoc_insertion_point(module_scope)
@@ -0,0 +1,132 @@
1
+ # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
2
+ """Client and server classes corresponding to protobuf-defined services."""
3
+ import grpc
4
+
5
+ import e6data_python_connector.cluster_server.cluster_pb2 as cluster__pb2
6
+
7
+
8
+ class ClusterServiceStub(object):
9
+ """Missing associated documentation comment in .proto file."""
10
+
11
+ def __init__(self, channel):
12
+ """Constructor.
13
+
14
+ Args:
15
+ channel: A grpc.Channel.
16
+ """
17
+ self.resume = channel.unary_unary(
18
+ '/ClusterService/resume',
19
+ request_serializer=cluster__pb2.ResumeRequest.SerializeToString,
20
+ response_deserializer=cluster__pb2.ResumeResponse.FromString,
21
+ )
22
+ self.suspend = channel.unary_unary(
23
+ '/ClusterService/suspend',
24
+ request_serializer=cluster__pb2.SuspendRequest.SerializeToString,
25
+ response_deserializer=cluster__pb2.SuspendResponse.FromString,
26
+ )
27
+ self.status = channel.unary_unary(
28
+ '/ClusterService/status',
29
+ request_serializer=cluster__pb2.ClusterStatusRequest.SerializeToString,
30
+ response_deserializer=cluster__pb2.ClusterStatusResponse.FromString,
31
+ )
32
+
33
+
34
+ class ClusterServiceServicer(object):
35
+ """Missing associated documentation comment in .proto file."""
36
+
37
+ def resume(self, request, context):
38
+ """Missing associated documentation comment in .proto file."""
39
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
40
+ context.set_details('Method not implemented!')
41
+ raise NotImplementedError('Method not implemented!')
42
+
43
+ def suspend(self, request, context):
44
+ """Missing associated documentation comment in .proto file."""
45
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
46
+ context.set_details('Method not implemented!')
47
+ raise NotImplementedError('Method not implemented!')
48
+
49
+ def status(self, request, context):
50
+ """Missing associated documentation comment in .proto file."""
51
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
52
+ context.set_details('Method not implemented!')
53
+ raise NotImplementedError('Method not implemented!')
54
+
55
+
56
+ def add_ClusterServiceServicer_to_server(servicer, server):
57
+ rpc_method_handlers = {
58
+ 'resume': grpc.unary_unary_rpc_method_handler(
59
+ servicer.resume,
60
+ request_deserializer=cluster__pb2.ResumeRequest.FromString,
61
+ response_serializer=cluster__pb2.ResumeResponse.SerializeToString,
62
+ ),
63
+ 'suspend': grpc.unary_unary_rpc_method_handler(
64
+ servicer.suspend,
65
+ request_deserializer=cluster__pb2.SuspendRequest.FromString,
66
+ response_serializer=cluster__pb2.SuspendResponse.SerializeToString,
67
+ ),
68
+ 'status': grpc.unary_unary_rpc_method_handler(
69
+ servicer.status,
70
+ request_deserializer=cluster__pb2.ClusterStatusRequest.FromString,
71
+ response_serializer=cluster__pb2.ClusterStatusResponse.SerializeToString,
72
+ ),
73
+ }
74
+ generic_handler = grpc.method_handlers_generic_handler(
75
+ 'ClusterService', rpc_method_handlers)
76
+ server.add_generic_rpc_handlers((generic_handler,))
77
+
78
+
79
+ # This class is part of an EXPERIMENTAL API.
80
+ class ClusterService(object):
81
+ """Missing associated documentation comment in .proto file."""
82
+
83
+ @staticmethod
84
+ def resume(request,
85
+ target,
86
+ options=(),
87
+ channel_credentials=None,
88
+ call_credentials=None,
89
+ insecure=False,
90
+ compression=None,
91
+ wait_for_ready=None,
92
+ timeout=None,
93
+ metadata=None):
94
+ return grpc.experimental.unary_unary(request, target, '/ClusterService/resume',
95
+ cluster__pb2.ResumeRequest.SerializeToString,
96
+ cluster__pb2.ResumeResponse.FromString,
97
+ options, channel_credentials,
98
+ insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
99
+
100
+ @staticmethod
101
+ def suspend(request,
102
+ target,
103
+ options=(),
104
+ channel_credentials=None,
105
+ call_credentials=None,
106
+ insecure=False,
107
+ compression=None,
108
+ wait_for_ready=None,
109
+ timeout=None,
110
+ metadata=None):
111
+ return grpc.experimental.unary_unary(request, target, '/ClusterService/suspend',
112
+ cluster__pb2.SuspendRequest.SerializeToString,
113
+ cluster__pb2.SuspendResponse.FromString,
114
+ options, channel_credentials,
115
+ insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
116
+
117
+ @staticmethod
118
+ def status(request,
119
+ target,
120
+ options=(),
121
+ channel_credentials=None,
122
+ call_credentials=None,
123
+ insecure=False,
124
+ compression=None,
125
+ wait_for_ready=None,
126
+ timeout=None,
127
+ metadata=None):
128
+ return grpc.experimental.unary_unary(request, target, '/ClusterService/status',
129
+ cluster__pb2.ClusterStatusRequest.SerializeToString,
130
+ cluster__pb2.ClusterStatusResponse.FromString,
131
+ options, channel_credentials,
132
+ insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
@@ -215,6 +215,7 @@ class E6dataDialect(default.DefaultDialect):
215
215
  catalog_name = None
216
216
  cluster_uuid = None
217
217
  secure = False
218
+ auto_resume = False
218
219
 
219
220
  def _dialect_specific_select_one(self):
220
221
  return "NOOP"
@@ -230,6 +231,7 @@ class E6dataDialect(default.DefaultDialect):
230
231
  self.catalog_name = url.query.get("catalog")
231
232
  self.cluster_uuid = url.query.get("cluster-uuid")
232
233
  self.secure = url.query.get("secure") == 'true'
234
+ self.auto_resume = url.query.get("auto-resume") == 'true'
233
235
  if not self.catalog_name:
234
236
  raise Exception('Please specify catalog in query parameter.')
235
237
 
@@ -242,7 +244,8 @@ class E6dataDialect(default.DefaultDialect):
242
244
  "database": database,
243
245
  "catalog": self.catalog_name,
244
246
  "cluster_uuid": self.cluster_uuid,
245
- 'secure': self.secure
247
+ 'secure': self.secure,
248
+ 'auto_resume': self.auto_resume
246
249
  }
247
250
  return [], kwargs
248
251
 
@@ -16,7 +16,9 @@ from io import BytesIO
16
16
  from ssl import CERT_NONE, CERT_OPTIONAL, CERT_REQUIRED
17
17
 
18
18
  import grpc
19
+ from grpc._channel import _InactiveRpcError
19
20
 
21
+ from e6data_python_connector.cluster_manager import ClusterManager
20
22
  from e6data_python_connector.common import DBAPITypeObject, ParamEscaper, DBAPICursor
21
23
  from e6data_python_connector.constants import *
22
24
  from e6data_python_connector.datainputstream import get_query_columns_info, read_rows_from_chunk
@@ -114,6 +116,7 @@ class Connection(object):
114
116
  database: str = None,
115
117
  cluster_uuid: str = None,
116
118
  secure: bool = False,
119
+ auto_resume: bool = False,
117
120
  grpc_options: dict = None,
118
121
  ):
119
122
  """
@@ -137,6 +140,8 @@ class Connection(object):
137
140
  Cluster's uuid
138
141
  secure: bool, Optional
139
142
  Flag to use a secure channel for data transfer
143
+ auto_resume: bool, Optional
144
+ Flag to enable auto resume of the cluster before the query execution
140
145
  grpc_options: dict, Optional
141
146
  Specify gRPC configuration
142
147
  - 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.
@@ -160,6 +165,8 @@ class Connection(object):
160
165
 
161
166
  self.catalog_name = catalog
162
167
 
168
+ self._auto_resume = auto_resume
169
+
163
170
  self._keepalive_timeout_ms = 900000
164
171
  self._max_receive_message_length = -1
165
172
  self._max_send_message_length = 300 * 1024 * 1024 # mb
@@ -213,6 +220,32 @@ class Connection(object):
213
220
  self._session_id = authenticate_response.sessionId
214
221
  if not self._session_id:
215
222
  raise ValueError("Invalid credentials.")
223
+ except _InactiveRpcError as e:
224
+ if self._auto_resume:
225
+ if e.code() == grpc.StatusCode.UNKNOWN and 'Stream removed' in e.details():
226
+ status = ClusterManager(
227
+ host=self._host,
228
+ port=self._port,
229
+ user=self.__username,
230
+ password=self.__password,
231
+ secure_channel=self._secure_channel
232
+ ).resume()
233
+ if status:
234
+ authenticate_request = e6x_engine_pb2.AuthenticateRequest(
235
+ user=self.__username,
236
+ password=self.__password
237
+ )
238
+ authenticate_response = self._client.authenticate(
239
+ authenticate_request,
240
+ metadata=_get_grpc_header(cluster=self.cluster_uuid)
241
+ )
242
+ self._session_id = authenticate_response.sessionId
243
+ else:
244
+ raise e
245
+ else:
246
+ raise e
247
+ else:
248
+ raise e
216
249
  except Exception as e:
217
250
  self._channel.close()
218
251
  raise e
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: e6data-python-connector
3
- Version: 2.1.21
3
+ Version: 2.2.0
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: Uniphi, Inc.
@@ -27,7 +27,7 @@ Requires-Dist: sqlalchemy
27
27
 
28
28
  # e6data Python Connector
29
29
 
30
- ![version](https://img.shields.io/badge/version-2.1.21-blue.svg)
30
+ ![version](https://img.shields.io/badge/version-2.2.0-blue.svg)
31
31
 
32
32
  ## Introduction
33
33
 
@@ -5,6 +5,7 @@ pyproject.toml
5
5
  setup.cfg
6
6
  setup.py
7
7
  e6data_python_connector/__init__.py
8
+ e6data_python_connector/cluster_manager.py
8
9
  e6data_python_connector/common.py
9
10
  e6data_python_connector/constants.py
10
11
  e6data_python_connector/datainputstream.py
@@ -19,6 +20,9 @@ e6data_python_connector.egg-info/dependency_links.txt
19
20
  e6data_python_connector.egg-info/entry_points.txt
20
21
  e6data_python_connector.egg-info/requires.txt
21
22
  e6data_python_connector.egg-info/top_level.txt
23
+ e6data_python_connector/cluster_server/__init__.py
24
+ e6data_python_connector/cluster_server/cluster_pb2.py
25
+ e6data_python_connector/cluster_server/cluster_pb2_grpc.py
22
26
  e6data_python_connector/e6x_vector/__init__.py
23
27
  e6data_python_connector/e6x_vector/constants.py
24
28
  e6data_python_connector/e6x_vector/ttypes.py
@@ -12,7 +12,7 @@
12
12
 
13
13
  import setuptools
14
14
 
15
- VERSION = [2, 1, 21]
15
+ VERSION = [2, 2, 0]
16
16
 
17
17
 
18
18
  def get_long_desc():