clickzetta-dbutils 1.0.4__py3-none-any.whl → 1.0.6__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- clickzetta_dbutils/__init__.py +2 -2
- clickzetta_dbutils/db_utils.py +86 -37
- clickzetta_dbutils/version.py +1 -1
- {clickzetta_dbutils-1.0.4.dist-info → clickzetta_dbutils-1.0.6.dist-info}/METADATA +1 -6
- clickzetta_dbutils-1.0.6.dist-info/RECORD +7 -0
- clickzetta_dbutils-1.0.4.dist-info/RECORD +0 -7
- {clickzetta_dbutils-1.0.4.dist-info → clickzetta_dbutils-1.0.6.dist-info}/WHEEL +0 -0
- {clickzetta_dbutils-1.0.4.dist-info → clickzetta_dbutils-1.0.6.dist-info}/top_level.txt +0 -0
clickzetta_dbutils/__init__.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
from .db_utils import get_active_engine,
|
1
|
+
from .db_utils import get_active_engine, get_lakehouse_connection, DatabaseConnectionManager, ConnectionConfig, \
|
2
2
|
DatabaseConnectionError
|
3
3
|
|
4
|
-
__all__ = ["get_active_engine", "
|
4
|
+
__all__ = ["get_active_engine", "get_lakehouse_connection", "DatabaseConnectionManager", "ConnectionConfig",
|
5
5
|
"DatabaseConnectionError"]
|
clickzetta_dbutils/db_utils.py
CHANGED
@@ -2,12 +2,15 @@ import json
|
|
2
2
|
import os
|
3
3
|
import urllib.parse
|
4
4
|
from dataclasses import dataclass, field
|
5
|
+
from logging import getLogger
|
5
6
|
from typing import Optional, Dict
|
6
7
|
|
7
8
|
from sqlalchemy import create_engine as sa_create_engine
|
8
9
|
from sqlalchemy.engine import URL
|
9
10
|
from sqlalchemy.engine.base import Engine
|
10
11
|
|
12
|
+
_log = getLogger(__name__)
|
13
|
+
|
11
14
|
|
12
15
|
class DatabaseConnectionError(Exception):
|
13
16
|
"""Custom exception for database connection errors."""
|
@@ -18,14 +21,16 @@ class DatabaseConnectionError(Exception):
|
|
18
21
|
class ConnectionConfig:
|
19
22
|
dsName: str
|
20
23
|
dsType: int
|
21
|
-
schema: str
|
24
|
+
schema: str = "public"
|
25
|
+
workspaceName: str = "default"
|
26
|
+
table: Optional[str] = None
|
22
27
|
host: Optional[str] = None
|
23
28
|
magicToken: Optional[str] = None
|
24
29
|
username: Optional[str] = None
|
25
30
|
password: Optional[str] = None
|
26
31
|
instanceName: Optional[str] = None
|
27
|
-
workspaceName: Optional[str] = None
|
28
32
|
options: Dict[str, str] = field(default_factory=dict)
|
33
|
+
query: Dict[str, str] = field(default_factory=dict)
|
29
34
|
|
30
35
|
|
31
36
|
class DatabaseConnectionManager:
|
@@ -33,16 +38,18 @@ class DatabaseConnectionManager:
|
|
33
38
|
Manages database connections with flexible configuration options.
|
34
39
|
"""
|
35
40
|
|
36
|
-
def __init__(self):
|
41
|
+
def __init__(self, ds_name: str):
|
37
42
|
"""
|
38
43
|
Initialize a database connection for a specific data source.
|
39
44
|
"""
|
45
|
+
self._ds_name = ds_name
|
40
46
|
self._vcluster: Optional[str] = None
|
41
47
|
self._workspace: Optional[str] = None
|
42
48
|
self._driver: Optional[str] = None
|
43
49
|
self._schema: Optional[str] = None
|
44
50
|
self._engine: Optional[Engine] = None
|
45
51
|
self._options = {}
|
52
|
+
self._query: Dict[str, str] = {}
|
46
53
|
|
47
54
|
@classmethod
|
48
55
|
def _load_connection_configs(cls) -> Dict[str, ConnectionConfig]:
|
@@ -55,6 +62,9 @@ class DatabaseConnectionManager:
|
|
55
62
|
if not hasattr(DatabaseConnectionManager, '_connection_cache'):
|
56
63
|
# Retrieve and decode connection info from environment variable
|
57
64
|
conn_info_str = os.environ.get('connectionInfos', '[]')
|
65
|
+
if not conn_info_str:
|
66
|
+
raise DatabaseConnectionError(
|
67
|
+
"No connection information found in environment variable 'connectionInfos'")
|
58
68
|
decoded_info = urllib.parse.unquote(conn_info_str)
|
59
69
|
conn_list = json.loads(decoded_info)
|
60
70
|
|
@@ -63,6 +73,10 @@ class DatabaseConnectionManager:
|
|
63
73
|
info.get('dsName'): ConnectionConfig(**info)
|
64
74
|
for info in conn_list
|
65
75
|
}
|
76
|
+
|
77
|
+
resource_names = [f"{conn.dsName} ({conn.workspaceName})" for conn in cls._connection_cache.values()]
|
78
|
+
_log.info(f"Successfully loaded connection configurations: {', '.join(resource_names)}")
|
79
|
+
|
66
80
|
return cls._connection_cache
|
67
81
|
|
68
82
|
def get_connection_info(self, ds_name: str) -> ConnectionConfig:
|
@@ -77,8 +91,16 @@ class DatabaseConnectionManager:
|
|
77
91
|
|
78
92
|
config = connections.get(ds_name)
|
79
93
|
config.options.update(self._options)
|
94
|
+
if self._query:
|
95
|
+
config.query.update(self._query)
|
80
96
|
return config
|
81
97
|
|
98
|
+
def get_connection_infos(self):
|
99
|
+
"""
|
100
|
+
Get all connection infos
|
101
|
+
"""
|
102
|
+
return self._load_connection_configs()
|
103
|
+
|
82
104
|
def use_workspace(self, workspace: str) -> 'DatabaseConnectionManager':
|
83
105
|
"""
|
84
106
|
Set workspace for the connection.
|
@@ -143,15 +165,27 @@ class DatabaseConnectionManager:
|
|
143
165
|
"""
|
144
166
|
if options:
|
145
167
|
self._options.update(options)
|
168
|
+
return self
|
169
|
+
|
170
|
+
def use_query(self, query: dict) -> 'DatabaseConnectionManager':
|
171
|
+
"""
|
172
|
+
Set query for the connection.
|
173
|
+
Args:
|
174
|
+
query (str): Query string
|
175
|
+
Returns:
|
176
|
+
self: For method chaining
|
177
|
+
"""
|
178
|
+
if query:
|
179
|
+
self._query.update(query)
|
180
|
+
return self
|
146
181
|
|
147
|
-
def
|
182
|
+
def build(self, *args, **kwargs) -> Engine:
|
148
183
|
"""
|
149
184
|
Create SQLAlchemy engine based on data source name and optional schema
|
150
185
|
|
151
|
-
:param ds_name: Name of the data source
|
152
186
|
:return: SQLAlchemy Engine
|
153
187
|
"""
|
154
|
-
conn_info: ConnectionConfig = self.get_connection_info(
|
188
|
+
conn_info: ConnectionConfig = self.get_connection_info(self._ds_name)
|
155
189
|
|
156
190
|
if not conn_info.host:
|
157
191
|
raise DatabaseConnectionError("Missing connection host for MySQL data source")
|
@@ -160,15 +194,14 @@ class DatabaseConnectionManager:
|
|
160
194
|
options = conn_info.options or {}
|
161
195
|
schema = self._schema or conn_info.schema
|
162
196
|
host_parts = conn_info.host.split(':')
|
163
|
-
|
164
|
-
|
197
|
+
connect_args = {}
|
165
198
|
|
166
199
|
# Construct connection URL based on data source type
|
167
200
|
if ds_type == 5: # Mysql
|
168
201
|
if not conn_info.username or not conn_info.password:
|
169
202
|
raise DatabaseConnectionError("Missing username or password for MySQL data source")
|
170
|
-
# Split host into host and port if provided
|
171
203
|
|
204
|
+
options.update(conn_info.query)
|
172
205
|
url = URL.create(
|
173
206
|
drivername=self._driver or 'mysql+mysqlconnector',
|
174
207
|
username=conn_info.username,
|
@@ -187,8 +220,11 @@ class DatabaseConnectionManager:
|
|
187
220
|
password=conn_info.password,
|
188
221
|
host=host_parts[0],
|
189
222
|
port=host_parts[1] if len(host_parts) > 1 else None,
|
190
|
-
database=schema
|
223
|
+
database=schema,
|
224
|
+
query=conn_info.query
|
191
225
|
)
|
226
|
+
connect_args = {'options': self._convert_options(options)}
|
227
|
+
|
192
228
|
elif ds_type == 1: # ClickZetta
|
193
229
|
if not conn_info.workspaceName or not conn_info.instanceName:
|
194
230
|
raise DatabaseConnectionError("Missing required parameters 'workspace_name', "
|
@@ -196,41 +232,53 @@ class DatabaseConnectionManager:
|
|
196
232
|
if not self._vcluster:
|
197
233
|
raise DatabaseConnectionError("Missing virtual cluster for ClickZetta data source")
|
198
234
|
|
235
|
+
# Generate base parameters
|
236
|
+
query_params = {
|
237
|
+
"virtualcluster": self._vcluster
|
238
|
+
}
|
239
|
+
|
240
|
+
if schema:
|
241
|
+
query_params["schema"] = schema
|
242
|
+
|
243
|
+
query_params.update(options)
|
244
|
+
query_params.update(conn_info.query)
|
245
|
+
|
246
|
+
full_host = f"{conn_info.instanceName}.{conn_info.host}"
|
247
|
+
|
199
248
|
if conn_info.username and conn_info.password:
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
249
|
+
url = URL.create(
|
250
|
+
drivername="clickzetta",
|
251
|
+
username=conn_info.username,
|
252
|
+
password=conn_info.password,
|
253
|
+
host=full_host,
|
254
|
+
database=conn_info.workspaceName,
|
255
|
+
query=query_params
|
256
|
+
)
|
205
257
|
elif conn_info.magicToken:
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
258
|
+
query_params["magic_token"] = conn_info.magicToken
|
259
|
+
url = URL.create(
|
260
|
+
drivername="clickzetta",
|
261
|
+
host=full_host,
|
262
|
+
database=conn_info.workspaceName,
|
263
|
+
query=query_params
|
264
|
+
)
|
211
265
|
else:
|
212
266
|
raise ValueError("username and password or token must be specified")
|
213
|
-
|
214
|
-
|
215
|
-
# Add schema if provided
|
216
|
-
if schema:
|
217
|
-
base_url += f"&schema={schema}"
|
218
|
-
|
219
|
-
url = base_url
|
220
267
|
else:
|
221
268
|
raise ValueError(f"Unsupported data source type: {ds_type}")
|
222
269
|
|
223
|
-
|
270
|
+
self._engine = sa_create_engine(url, connect_args=connect_args, *args, **kwargs)
|
271
|
+
return self._engine
|
224
272
|
|
225
273
|
@staticmethod
|
226
274
|
def _convert_options(options):
|
227
275
|
if not options:
|
228
276
|
return ''
|
229
|
-
return ' '.join([f'-c
|
277
|
+
return ' '.join([f'-c{k}={v}' for k, v in options.items()])
|
230
278
|
|
231
279
|
|
232
|
-
def
|
233
|
-
return conn.connection.connection
|
280
|
+
def get_lakehouse_connection(conn):
|
281
|
+
return conn.connection.connection
|
234
282
|
|
235
283
|
|
236
284
|
def get_active_engine(
|
@@ -245,15 +293,16 @@ def get_active_engine(
|
|
245
293
|
Convenience function to create a database engine.
|
246
294
|
|
247
295
|
Args:
|
248
|
-
ds_name (str): Data source name
|
249
|
-
|
250
|
-
|
251
|
-
|
296
|
+
ds_name (str): Data source name. Required.
|
297
|
+
vcluster (str, optional): Virtual cluster name for ClickZetta data source. Required for ClickZetta.
|
298
|
+
workspace (str, optional): Workspace name. Default is 'default'.
|
299
|
+
schema (str, optional): Schema name for the connection. Default is 'public'.
|
300
|
+
options (dict, optional): Additional connection options.
|
252
301
|
|
253
302
|
Returns:
|
254
303
|
SQLAlchemy Engine instance
|
255
304
|
"""
|
256
|
-
manager = DatabaseConnectionManager()
|
305
|
+
manager = DatabaseConnectionManager(ds_name)
|
257
306
|
|
258
307
|
if workspace:
|
259
308
|
manager.use_workspace(workspace)
|
@@ -264,4 +313,4 @@ def get_active_engine(
|
|
264
313
|
if options:
|
265
314
|
manager.use_options(options)
|
266
315
|
|
267
|
-
return manager.
|
316
|
+
return manager.build(*args, **kwargs)
|
clickzetta_dbutils/version.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "1.0.
|
1
|
+
__version__ = "1.0.6"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: clickzetta-dbutils
|
3
|
-
Version: 1.0.
|
3
|
+
Version: 1.0.6
|
4
4
|
Summary: clickzetta dbutils
|
5
5
|
Author-email: "lin.zhang" <lin.zhang@clickzetta.com>
|
6
6
|
Project-URL: documentation, https://www.yunqi.tech/
|
@@ -14,13 +14,8 @@ Requires-Dist: psycopg2-binary
|
|
14
14
|
Requires-Dist: mysql-connector-python
|
15
15
|
Provides-Extra: dev
|
16
16
|
Requires-Dist: pytest==8.2.1; extra == "dev"
|
17
|
-
Requires-Dist: sqlparse; extra == "dev"
|
18
|
-
Requires-Dist: grpcio; extra == "dev"
|
19
|
-
Requires-Dist: grpcio-tools; extra == "dev"
|
20
17
|
Requires-Dist: build; extra == "dev"
|
21
18
|
Requires-Dist: pytest-xdist; extra == "dev"
|
22
|
-
Requires-Dist: pytz; extra == "dev"
|
23
|
-
Requires-Dist: apache-superset==4.0.2; extra == "dev"
|
24
19
|
Provides-Extra: cliclzetta
|
25
20
|
Requires-Dist: clickzetta-connector; extra == "cliclzetta"
|
26
21
|
Requires-Dist: clickzetta-sqlalchemy; extra == "cliclzetta"
|
@@ -0,0 +1,7 @@
|
|
1
|
+
clickzetta_dbutils/__init__.py,sha256=Q_6kas0RvZ0767qlaA_xGESmXxm0dks1YQ8DqCX8LV0,290
|
2
|
+
clickzetta_dbutils/db_utils.py,sha256=1iVaiYIQosukrQBAbgjNXJJW51p7k1ihQKBIcl_MvQM,10166
|
3
|
+
clickzetta_dbutils/version.py,sha256=fCDDAyG3nMZcE_hvt1RHdxkiFN3DfNSyU_k-rLUDrpE,21
|
4
|
+
clickzetta_dbutils-1.0.6.dist-info/METADATA,sha256=mpfK67NkOGCBN_wu5NcTvMLLTsVGlLxmnNdcX3KTiFw,726
|
5
|
+
clickzetta_dbutils-1.0.6.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
6
|
+
clickzetta_dbutils-1.0.6.dist-info/top_level.txt,sha256=8o5KqMSg9pxnPNejHjMaqZV2vEDvwvsz2GdChZI0N6I,19
|
7
|
+
clickzetta_dbutils-1.0.6.dist-info/RECORD,,
|
@@ -1,7 +0,0 @@
|
|
1
|
-
clickzetta_dbutils/__init__.py,sha256=OevYNnzvgLUw0-KDJKG7loCONkN_7DxtmqZAmzCHGAg,282
|
2
|
-
clickzetta_dbutils/db_utils.py,sha256=L170MOzkMH4cfEwYJcHPz7_aQcTi96e9BqkF6CCRfc4,8506
|
3
|
-
clickzetta_dbutils/version.py,sha256=O-a1T6uLdX0DYXelAnzqY4NDGktopbWdpKZloec7oxY,21
|
4
|
-
clickzetta_dbutils-1.0.4.dist-info/METADATA,sha256=5Q0gbUScv8hzvjZxf_gaJaZJiGqakvkFIilkrauCgbw,938
|
5
|
-
clickzetta_dbutils-1.0.4.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
6
|
-
clickzetta_dbutils-1.0.4.dist-info/top_level.txt,sha256=8o5KqMSg9pxnPNejHjMaqZV2vEDvwvsz2GdChZI0N6I,19
|
7
|
-
clickzetta_dbutils-1.0.4.dist-info/RECORD,,
|
File without changes
|
File without changes
|