clickzetta-dbutils 1.0.3__py3-none-any.whl → 1.0.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
- from .db_utils import get_active_engine, get_lakehouse_client, DatabaseConnectionManager, ConnectionConfig, \
1
+ from .db_utils import get_active_engine, get_lakehouse_connection, DatabaseConnectionManager, ConnectionConfig, \
2
2
  DatabaseConnectionError
3
3
 
4
- __all__ = ["get_active_engine", "get_lakehouse_client", "DatabaseConnectionManager", "ConnectionConfig",
4
+ __all__ = ["get_active_engine", "get_lakehouse_connection", "DatabaseConnectionManager", "ConnectionConfig",
5
5
  "DatabaseConnectionError"]
@@ -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,15 @@ 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"
22
26
  host: Optional[str] = None
23
27
  magicToken: Optional[str] = None
24
28
  username: Optional[str] = None
25
29
  password: Optional[str] = None
26
30
  instanceName: Optional[str] = None
27
- workspaceName: Optional[str] = None
28
31
  options: Dict[str, str] = field(default_factory=dict)
32
+ query: Dict[str, str] = field(default_factory=dict)
29
33
 
30
34
 
31
35
  class DatabaseConnectionManager:
@@ -33,16 +37,18 @@ class DatabaseConnectionManager:
33
37
  Manages database connections with flexible configuration options.
34
38
  """
35
39
 
36
- def __init__(self):
40
+ def __init__(self, ds_name: str):
37
41
  """
38
42
  Initialize a database connection for a specific data source.
39
43
  """
44
+ self._ds_name = ds_name
40
45
  self._vcluster: Optional[str] = None
41
46
  self._workspace: Optional[str] = None
42
47
  self._driver: Optional[str] = None
43
48
  self._schema: Optional[str] = None
44
49
  self._engine: Optional[Engine] = None
45
50
  self._options = {}
51
+ self._query: Dict[str, str] = {}
46
52
 
47
53
  @classmethod
48
54
  def _load_connection_configs(cls) -> Dict[str, ConnectionConfig]:
@@ -55,6 +61,9 @@ class DatabaseConnectionManager:
55
61
  if not hasattr(DatabaseConnectionManager, '_connection_cache'):
56
62
  # Retrieve and decode connection info from environment variable
57
63
  conn_info_str = os.environ.get('connectionInfos', '[]')
64
+ if not conn_info_str:
65
+ raise DatabaseConnectionError(
66
+ "No connection information found in environment variable 'connectionInfos'")
58
67
  decoded_info = urllib.parse.unquote(conn_info_str)
59
68
  conn_list = json.loads(decoded_info)
60
69
 
@@ -63,6 +72,10 @@ class DatabaseConnectionManager:
63
72
  info.get('dsName'): ConnectionConfig(**info)
64
73
  for info in conn_list
65
74
  }
75
+
76
+ resource_names = [f"{conn.dsName} ({conn.workspaceName})" for conn in cls._connection_cache.values()]
77
+ _log.info(f"Successfully loaded connection configurations: {', '.join(resource_names)}")
78
+
66
79
  return cls._connection_cache
67
80
 
68
81
  def get_connection_info(self, ds_name: str) -> ConnectionConfig:
@@ -77,8 +90,16 @@ class DatabaseConnectionManager:
77
90
 
78
91
  config = connections.get(ds_name)
79
92
  config.options.update(self._options)
93
+ if self._query:
94
+ config.query.update(self._query)
80
95
  return config
81
96
 
97
+ def get_connection_infos(self):
98
+ """
99
+ Get all connection infos
100
+ """
101
+ return self._load_connection_configs()
102
+
82
103
  def use_workspace(self, workspace: str) -> 'DatabaseConnectionManager':
83
104
  """
84
105
  Set workspace for the connection.
@@ -143,15 +164,27 @@ class DatabaseConnectionManager:
143
164
  """
144
165
  if options:
145
166
  self._options.update(options)
167
+ return self
168
+
169
+ def use_query(self, query: dict) -> 'DatabaseConnectionManager':
170
+ """
171
+ Set query for the connection.
172
+ Args:
173
+ query (str): Query string
174
+ Returns:
175
+ self: For method chaining
176
+ """
177
+ if query:
178
+ self._query.update(query)
179
+ return self
146
180
 
147
- def connect(self, ds_name: str, *args, **kwargs) -> Engine:
181
+ def build(self, *args, **kwargs) -> Engine:
148
182
  """
149
183
  Create SQLAlchemy engine based on data source name and optional schema
150
184
 
151
- :param ds_name: Name of the data source
152
185
  :return: SQLAlchemy Engine
153
186
  """
154
- conn_info: ConnectionConfig = self.get_connection_info(ds_name)
187
+ conn_info: ConnectionConfig = self.get_connection_info(self._ds_name)
155
188
 
156
189
  if not conn_info.host:
157
190
  raise DatabaseConnectionError("Missing connection host for MySQL data source")
@@ -160,15 +193,14 @@ class DatabaseConnectionManager:
160
193
  options = conn_info.options or {}
161
194
  schema = self._schema or conn_info.schema
162
195
  host_parts = conn_info.host.split(':')
163
-
164
-
196
+ connect_args = {}
165
197
 
166
198
  # Construct connection URL based on data source type
167
199
  if ds_type == 5: # Mysql
168
200
  if not conn_info.username or not conn_info.password:
169
201
  raise DatabaseConnectionError("Missing username or password for MySQL data source")
170
- # Split host into host and port if provided
171
202
 
203
+ options.update(conn_info.query)
172
204
  url = URL.create(
173
205
  drivername=self._driver or 'mysql+mysqlconnector',
174
206
  username=conn_info.username,
@@ -187,8 +219,10 @@ class DatabaseConnectionManager:
187
219
  password=conn_info.password,
188
220
  host=host_parts[0],
189
221
  port=host_parts[1] if len(host_parts) > 1 else None,
190
- database=schema
222
+ database=schema,
223
+ query=conn_info.query
191
224
  )
225
+ connect_args = {'options': self._convert_options(options)}
192
226
  elif ds_type == 1: # ClickZetta
193
227
  if not conn_info.workspaceName or not conn_info.instanceName:
194
228
  raise DatabaseConnectionError("Missing required parameters 'workspace_name', "
@@ -211,16 +245,22 @@ class DatabaseConnectionManager:
211
245
  else:
212
246
  raise ValueError("username and password or token must be specified")
213
247
 
214
-
215
248
  # Add schema if provided
216
249
  if schema:
217
250
  base_url += f"&schema={schema}"
218
251
 
252
+ options.update(conn_info.query)
253
+
254
+ # Add query string if provided
255
+ if options:
256
+ base_url += f"&{urllib.parse.urlencode(options)}"
257
+
219
258
  url = base_url
220
259
  else:
221
260
  raise ValueError(f"Unsupported data source type: {ds_type}")
222
261
 
223
- return sa_create_engine(url, connect_args={'options': self._convert_options(options)}, *args, **kwargs)
262
+ self._engine = sa_create_engine(url, connect_args=connect_args, *args, **kwargs)
263
+ return self._engine
224
264
 
225
265
  @staticmethod
226
266
  def _convert_options(options):
@@ -229,8 +269,8 @@ class DatabaseConnectionManager:
229
269
  return ' '.join([f'-c {k}={v}' for k, v in options.items()])
230
270
 
231
271
 
232
- def get_lakehouse_client(conn):
233
- return conn.connection.connection._client
272
+ def get_lakehouse_connection(conn):
273
+ return conn.connection.connection
234
274
 
235
275
 
236
276
  def get_active_engine(
@@ -245,15 +285,16 @@ def get_active_engine(
245
285
  Convenience function to create a database engine.
246
286
 
247
287
  Args:
248
- ds_name (str): Data source name
249
- workspace (str, optional): Workspace name
250
- schema (str, optional): Schema name
251
- vcluster (str, optional): Virtual cluster name
288
+ ds_name (str): Data source name. Required.
289
+ vcluster (str, optional): Virtual cluster name for ClickZetta data source. Required for ClickZetta.
290
+ workspace (str, optional): Workspace name. Default is 'default'.
291
+ schema (str, optional): Schema name for the connection. Default is 'public'.
292
+ options (dict, optional): Additional connection options.
252
293
 
253
294
  Returns:
254
295
  SQLAlchemy Engine instance
255
296
  """
256
- manager = DatabaseConnectionManager()
297
+ manager = DatabaseConnectionManager(ds_name)
257
298
 
258
299
  if workspace:
259
300
  manager.use_workspace(workspace)
@@ -264,4 +305,4 @@ def get_active_engine(
264
305
  if options:
265
306
  manager.use_options(options)
266
307
 
267
- return manager.connect(ds_name, *args, **kwargs)
308
+ return manager.build(*args, **kwargs)
@@ -1 +1 @@
1
- __version__ = "1.0.3"
1
+ __version__ = "1.0.5"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: clickzetta-dbutils
3
- Version: 1.0.3
3
+ Version: 1.0.5
4
4
  Summary: clickzetta dbutils
5
5
  Author-email: "lin.zhang" <lin.zhang@clickzetta.com>
6
6
  Project-URL: documentation, https://www.yunqi.tech/
@@ -10,7 +10,7 @@ Classifier: Development Status :: 3 - Alpha
10
10
  Requires-Python: >=3.7
11
11
  Requires-Dist: sqlalchemy<2.0.0,>=1.4.0
12
12
  Requires-Dist: numpy==1.24.4
13
- Requires-Dist: psycopg2
13
+ 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"
@@ -0,0 +1,7 @@
1
+ clickzetta_dbutils/__init__.py,sha256=Q_6kas0RvZ0767qlaA_xGESmXxm0dks1YQ8DqCX8LV0,290
2
+ clickzetta_dbutils/db_utils.py,sha256=TQobsTlxNTzUwIFgWZvVMtydNUPk_7AG82ChWQ4BCQY,10085
3
+ clickzetta_dbutils/version.py,sha256=ZR1VA9cGs0vIK6cWK4YKLfBTnmUCAcDaaP9ARPPYxEs,21
4
+ clickzetta_dbutils-1.0.5.dist-info/METADATA,sha256=2QYdvLVx4oQCI0J727G0ACdKrwuNAlYXjPpUKuSS6mI,938
5
+ clickzetta_dbutils-1.0.5.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
6
+ clickzetta_dbutils-1.0.5.dist-info/top_level.txt,sha256=8o5KqMSg9pxnPNejHjMaqZV2vEDvwvsz2GdChZI0N6I,19
7
+ clickzetta_dbutils-1.0.5.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=-PYTKPEkq2aGJHOdzkgu5oKZIPKSq6MCqGrFyy4QQXc,21
4
- clickzetta_dbutils-1.0.3.dist-info/METADATA,sha256=Io3EEH8-lVA9DMp33N7x8WDgMPeeWHkzY9Qi4GDwPg4,931
5
- clickzetta_dbutils-1.0.3.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
6
- clickzetta_dbutils-1.0.3.dist-info/top_level.txt,sha256=8o5KqMSg9pxnPNejHjMaqZV2vEDvwvsz2GdChZI0N6I,19
7
- clickzetta_dbutils-1.0.3.dist-info/RECORD,,