db_client_toolkit 0.0.1__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.
@@ -0,0 +1,321 @@
1
+ """
2
+ Redis数据库客户端
3
+ """
4
+
5
+ from typing import Any, Dict, List, Optional, Union
6
+ import logging
7
+ import json
8
+
9
+ from ..core.base import BaseClient
10
+ from ..exceptions import ConnectionError, QueryError, NotSupportedError
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class RedisClient(BaseClient):
16
+ """Redis数据库客户端实现"""
17
+
18
+ def _validate_config(self) -> None:
19
+ """验证Redis配置"""
20
+ super()._validate_config()
21
+ # Redis的配置比较灵活,不强制要求特定字段
22
+
23
+ def connect(self) -> bool:
24
+ """连接Redis数据库"""
25
+ try:
26
+ import redis
27
+
28
+ self.connection = redis.Redis(
29
+ host=self.config.get('host', 'localhost'),
30
+ port=self.config.get('port', 6379),
31
+ password=self.config.get('password'),
32
+ db=self.config.get('db', 0),
33
+ decode_responses=True,
34
+ **self.config.get('options', {})
35
+ )
36
+
37
+ # 测试连接
38
+ self.connection.ping()
39
+
40
+ self._connected = True
41
+ logger.info(f"Redis连接成功: {self.config.get('host', 'localhost')}:{self.config.get('port', 6379)}")
42
+ return True
43
+
44
+ except ImportError:
45
+ raise ConnectionError("未安装redis库,请运行: pip install redis")
46
+ except Exception as e:
47
+ self._connected = False
48
+ logger.error(f"Redis连接失败: {e}")
49
+ raise ConnectionError(f"Redis连接失败: {e}")
50
+
51
+ def disconnect(self) -> bool:
52
+ """断开Redis连接"""
53
+ if self.connection:
54
+ try:
55
+ self.connection.close()
56
+ self._connected = False
57
+ logger.info("Redis连接已关闭")
58
+ return True
59
+ except Exception as e:
60
+ logger.error(f"关闭Redis连接失败: {e}")
61
+ return False
62
+ return True
63
+
64
+ def is_connected(self) -> bool:
65
+ """检查Redis连接状态"""
66
+ if not self.connection:
67
+ return False
68
+ try:
69
+ self.connection.ping()
70
+ return True
71
+ except Exception:
72
+ self._connected = False
73
+ return False
74
+
75
+ def execute(self, query: str, params: Optional[tuple] = None) -> Any:
76
+ """
77
+ Redis不支持SQL查询
78
+
79
+ Raises:
80
+ NotSupportedError: 总是抛出此异常
81
+ """
82
+ raise NotSupportedError("Redis不支持SQL查询,请使用专用方法")
83
+
84
+ # ============ 基础键值操作 ============
85
+
86
+ def get(self, key: str) -> Optional[str]:
87
+ """
88
+ 获取键值
89
+
90
+ Args:
91
+ key: 键名
92
+
93
+ Returns:
94
+ Optional[str]: 值,不存在返回None
95
+ """
96
+ if not self.is_connected():
97
+ raise ConnectionError("数据库未连接")
98
+
99
+ try:
100
+ return self.connection.get(key)
101
+ except Exception as e:
102
+ logger.error(f"获取键值失败: {e}")
103
+ raise QueryError(f"获取键值失败: {e}")
104
+
105
+ def set(self, key: str, value: str, ex: Optional[int] = None,
106
+ nx: bool = False, xx: bool = False) -> bool:
107
+ """
108
+ 设置键值
109
+
110
+ Args:
111
+ key: 键名
112
+ value: 值
113
+ ex: 过期时间(秒)
114
+ nx: 只在键不存在时设置
115
+ xx: 只在键存在时设置
116
+
117
+ Returns:
118
+ bool: 是否设置成功
119
+ """
120
+ if not self.is_connected():
121
+ raise ConnectionError("数据库未连接")
122
+
123
+ try:
124
+ return self.connection.set(key, value, ex=ex, nx=nx, xx=xx)
125
+ except Exception as e:
126
+ logger.error(f"设置键值失败: {e}")
127
+ raise QueryError(f"设置键值失败: {e}")
128
+
129
+ def delete_key(self, *keys: str) -> int:
130
+ """
131
+ 删除键
132
+
133
+ Args:
134
+ *keys: 要删除的键
135
+
136
+ Returns:
137
+ int: 删除的键数量
138
+ """
139
+ if not self.is_connected():
140
+ raise ConnectionError("数据库未连接")
141
+
142
+ try:
143
+ return self.connection.delete(*keys)
144
+ except Exception as e:
145
+ logger.error(f"删除键失败: {e}")
146
+ raise QueryError(f"删除键失败: {e}")
147
+
148
+ def exists_key(self, *keys: str) -> int:
149
+ """
150
+ 检查键是否存在
151
+
152
+ Args:
153
+ *keys: 要检查的键
154
+
155
+ Returns:
156
+ int: 存在的键数量
157
+ """
158
+ if not self.is_connected():
159
+ raise ConnectionError("数据库未连接")
160
+
161
+ try:
162
+ return self.connection.exists(*keys)
163
+ except Exception as e:
164
+ logger.error(f"检查键存在失败: {e}")
165
+ raise QueryError(f"检查键存在失败: {e}")
166
+
167
+ # ============ Hash操作 ============
168
+
169
+ def hget(self, name: str, key: str) -> Optional[str]:
170
+ """获取Hash字段值"""
171
+ if not self.is_connected():
172
+ raise ConnectionError("数据库未连接")
173
+
174
+ try:
175
+ return self.connection.hget(name, key)
176
+ except Exception as e:
177
+ logger.error(f"获取Hash字段失败: {e}")
178
+ raise QueryError(f"获取Hash字段失败: {e}")
179
+
180
+ def hset(self, name: str, key: str = None, value: str = None,
181
+ mapping: Dict[str, Any] = None) -> int:
182
+ """设置Hash字段"""
183
+ if not self.is_connected():
184
+ raise ConnectionError("数据库未连接")
185
+
186
+ try:
187
+ if mapping:
188
+ return self.connection.hset(name, mapping=mapping)
189
+ else:
190
+ return self.connection.hset(name, key, value)
191
+ except Exception as e:
192
+ logger.error(f"设置Hash字段失败: {e}")
193
+ raise QueryError(f"设置Hash字段失败: {e}")
194
+
195
+ def hgetall(self, name: str) -> Dict[str, str]:
196
+ """获取Hash所有字段"""
197
+ if not self.is_connected():
198
+ raise ConnectionError("数据库未连接")
199
+
200
+ try:
201
+ return self.connection.hgetall(name)
202
+ except Exception as e:
203
+ logger.error(f"获取Hash所有字段失败: {e}")
204
+ raise QueryError(f"获取Hash所有字段失败: {e}")
205
+
206
+ # ============ 实现基类抽象方法(使用Hash存储) ============
207
+
208
+ def insert(self, table: str, data: Dict[str, Any]) -> Any:
209
+ """
210
+ 使用Hash存储数据
211
+
212
+ Args:
213
+ table: 表名(用作键前缀)
214
+ data: 数据,必须包含'id'字段
215
+
216
+ Returns:
217
+ str: 键名
218
+ """
219
+ if not self.is_connected():
220
+ raise ConnectionError("数据库未连接")
221
+
222
+ # 获取或生成ID
223
+ if 'id' in data:
224
+ record_id = data['id']
225
+ else:
226
+ record_id = self.connection.incr(f'{table}:id')
227
+ data['id'] = str(record_id)
228
+
229
+ key = f"{table}:{record_id}"
230
+
231
+ # 将数据转换为字符串存储
232
+ str_data = {k: json.dumps(v) if isinstance(v, (dict, list)) else str(v)
233
+ for k, v in data.items()}
234
+
235
+ try:
236
+ self.hset(key, mapping=str_data)
237
+ logger.debug(f"插入数据成功,键: {key}")
238
+ return key
239
+ except Exception as e:
240
+ logger.error(f"插入数据失败: {e}")
241
+ raise QueryError(f"插入数据失败: {e}")
242
+
243
+ def update(self, table: str, data: Dict[str, Any],
244
+ condition: Dict[str, Any]) -> int:
245
+ """更新Hash数据"""
246
+ if not self.is_connected():
247
+ raise ConnectionError("数据库未连接")
248
+
249
+ if 'id' not in condition:
250
+ raise ValueError("Redis更新操作需要提供id条件")
251
+
252
+ key = f"{table}:{condition['id']}"
253
+
254
+ if not self.exists_key(key):
255
+ return 0
256
+
257
+ # 转换数据
258
+ str_data = {k: json.dumps(v) if isinstance(v, (dict, list)) else str(v)
259
+ for k, v in data.items()}
260
+
261
+ try:
262
+ self.hset(key, mapping=str_data)
263
+ logger.debug(f"更新数据成功,键: {key}")
264
+ return 1
265
+ except Exception as e:
266
+ logger.error(f"更新数据失败: {e}")
267
+ raise QueryError(f"更新数据失败: {e}")
268
+
269
+ def delete(self, table: str, condition: Dict[str, Any]) -> int:
270
+ """删除Hash数据"""
271
+ if not self.is_connected():
272
+ raise ConnectionError("数据库未连接")
273
+
274
+ if 'id' not in condition:
275
+ raise ValueError("Redis删除操作需要提供id条件")
276
+
277
+ key = f"{table}:{condition['id']}"
278
+
279
+ try:
280
+ count = self.delete_key(key)
281
+ logger.debug(f"删除了 {count} 条数据")
282
+ return count
283
+ except Exception as e:
284
+ logger.error(f"删除数据失败: {e}")
285
+ raise QueryError(f"删除数据失败: {e}")
286
+
287
+ def select(self, table: str,
288
+ fields: Optional[List[str]] = None,
289
+ condition: Optional[Dict[str, Any]] = None,
290
+ limit: Optional[int] = None,
291
+ offset: Optional[int] = None,
292
+ order_by: Optional[List[tuple]] = None) -> List[Dict]:
293
+ """查询Hash数据"""
294
+ if not self.is_connected():
295
+ raise ConnectionError("数据库未连接")
296
+
297
+ # Redis简化实现,只支持通过id查询
298
+ if not condition or 'id' not in condition:
299
+ logger.warning("Redis select操作建议提供id条件以提高性能")
300
+ return []
301
+
302
+ key = f"{table}:{condition['id']}"
303
+
304
+ try:
305
+ data = self.hgetall(key)
306
+ if not data:
307
+ return []
308
+
309
+ # 尝试解析JSON
310
+ result = {}
311
+ for k, v in data.items():
312
+ try:
313
+ result[k] = json.loads(v)
314
+ except (json.JSONDecodeError, ValueError):
315
+ result[k] = v
316
+
317
+ return [result]
318
+
319
+ except Exception as e:
320
+ logger.error(f"查询数据失败: {e}")
321
+ raise QueryError(f"查询数据失败: {e}")
@@ -0,0 +1,152 @@
1
+ """
2
+ SQLite数据库客户端
3
+ """
4
+
5
+ from typing import Any, Dict, List, Optional
6
+ import logging
7
+ import sqlite3
8
+
9
+ from ..core.sql_base import SQLBaseClient
10
+ from ..exceptions import ConnectionError, QueryError
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class SQLiteClient(SQLBaseClient):
16
+ """SQLite数据库客户端实现"""
17
+
18
+ @property
19
+ def placeholder(self) -> str:
20
+ return "?"
21
+
22
+ def _validate_config(self) -> None:
23
+ """验证SQLite配置"""
24
+ super()._validate_config()
25
+ if 'database' not in self.config:
26
+ raise ValueError("SQLite配置缺少必需字段: database")
27
+
28
+ def connect(self) -> bool:
29
+ """连接SQLite数据库"""
30
+ try:
31
+ database = self.config.get('database', ':memory:')
32
+ self.connection = sqlite3.connect(database)
33
+ self.connection.row_factory = sqlite3.Row
34
+ self._connected = True
35
+ logger.info(f"SQLite连接成功: {database}")
36
+ return True
37
+
38
+ except Exception as e:
39
+ self._connected = False
40
+ logger.error(f"SQLite连接失败: {e}")
41
+ raise ConnectionError(f"SQLite连接失败: {e}")
42
+
43
+ def disconnect(self) -> bool:
44
+ """断开SQLite连接"""
45
+ if self.connection:
46
+ try:
47
+ self.connection.close()
48
+ self._connected = False
49
+ logger.info("SQLite连接已关闭")
50
+ return True
51
+ except Exception as e:
52
+ logger.error(f"关闭SQLite连接失败: {e}")
53
+ return False
54
+ return True
55
+
56
+ def is_connected(self) -> bool:
57
+ """检查SQLite连接状态"""
58
+ if not self.connection:
59
+ return False
60
+ try:
61
+ # 尝试执行一个简单的查询
62
+ self.connection.execute("SELECT 1")
63
+ return True
64
+ except Exception:
65
+ self._connected = False
66
+ return False
67
+
68
+ def execute(self, query: str, params: Optional[tuple] = None) -> Any:
69
+ """执行SQLite查询"""
70
+ if not self.is_connected():
71
+ raise ConnectionError("数据库未连接")
72
+
73
+ try:
74
+ cursor = self.connection.cursor()
75
+ cursor.execute(query, params or ())
76
+
77
+ if query.strip().upper().startswith('SELECT'):
78
+ result = [dict(row) for row in cursor.fetchall()]
79
+ else:
80
+ self.connection.commit()
81
+ result = cursor.rowcount
82
+
83
+ cursor.close()
84
+ return result
85
+
86
+ except Exception as e:
87
+ logger.error(f"查询执行失败: {e}")
88
+ raise QueryError(f"查询执行失败: {e}")
89
+
90
+ def insert(self, table: str, data: Dict[str, Any]) -> Any:
91
+ """插入数据到SQLite"""
92
+ query, params = self._build_insert_query(table, data)
93
+
94
+ try:
95
+ cursor = self.connection.cursor()
96
+ cursor.execute(query, params)
97
+ self.connection.commit()
98
+ last_id = cursor.lastrowid
99
+ cursor.close()
100
+ logger.debug(f"插入数据成功,ID: {last_id}")
101
+ return last_id
102
+
103
+ except Exception as e:
104
+ logger.error(f"插入数据失败: {e}")
105
+ raise QueryError(f"插入数据失败: {e}")
106
+
107
+ def update(self, table: str, data: Dict[str, Any],
108
+ condition: Dict[str, Any]) -> int:
109
+ """更新SQLite数据"""
110
+ query, params = self._build_update_query(table, data, condition)
111
+ result = self.execute(query, params)
112
+ logger.debug(f"更新了 {result} 条记录")
113
+ return result
114
+
115
+ def delete(self, table: str, condition: Dict[str, Any]) -> int:
116
+ """删除SQLite数据"""
117
+ query, params = self._build_delete_query(table, condition)
118
+ result = self.execute(query, params)
119
+ logger.debug(f"删除了 {result} 条记录")
120
+ return result
121
+
122
+ def select(self, table: str,
123
+ fields: Optional[List[str]] = None,
124
+ condition: Optional[Dict[str, Any]] = None,
125
+ limit: Optional[int] = None,
126
+ offset: Optional[int] = None,
127
+ order_by: Optional[List[tuple]] = None) -> List[Dict]:
128
+ """查询SQLite数据"""
129
+ query, params = self._build_select_query(
130
+ table, fields, condition, limit, offset, order_by
131
+ )
132
+ result = self.execute(query, params)
133
+ logger.debug(f"查询到 {len(result)} 条记录")
134
+ return result
135
+
136
+ def execute_script(self, script: str) -> None:
137
+ """
138
+ 执行SQL脚本
139
+
140
+ Args:
141
+ script: SQL脚本内容
142
+ """
143
+ if not self.is_connected():
144
+ raise ConnectionError("数据库未连接")
145
+
146
+ try:
147
+ self.connection.executescript(script)
148
+ self.connection.commit()
149
+ logger.debug("SQL脚本执行成功")
150
+ except Exception as e:
151
+ logger.error(f"SQL脚本执行失败: {e}")
152
+ raise QueryError(f"SQL脚本执行失败: {e}")
@@ -0,0 +1,230 @@
1
+ """
2
+ Supabase数据库客户端
3
+ """
4
+
5
+ from typing import Any, Dict, List, Optional, Union
6
+ import logging
7
+
8
+ from ..core.base import BaseClient
9
+ from ..exceptions import ConnectionError, QueryError, NotSupportedError
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class SupabaseClient(BaseClient):
15
+ """Supabase数据库客户端实现"""
16
+
17
+ def _validate_config(self) -> None:
18
+ """验证Supabase配置"""
19
+ super()._validate_config()
20
+ required = ['url', 'key']
21
+ missing = [key for key in required if key not in self.config]
22
+ if missing:
23
+ raise ValueError(f"Supabase配置缺少必需字段: {', '.join(missing)}")
24
+
25
+ def connect(self) -> bool:
26
+ """连接Supabase"""
27
+ try:
28
+ from supabase import create_client, Client
29
+
30
+ url = self.config['url']
31
+ key = self.config['key']
32
+
33
+ self.connection: Client = create_client(url, key)
34
+ self._connected = True
35
+ logger.info(f"Supabase连接成功: {url}")
36
+ return True
37
+
38
+ except ImportError:
39
+ raise ConnectionError("未安装supabase库,请运行: pip install supabase")
40
+ except Exception as e:
41
+ self._connected = False
42
+ logger.error(f"Supabase连接失败: {e}")
43
+ raise ConnectionError(f"Supabase连接失败: {e}")
44
+
45
+ def disconnect(self) -> bool:
46
+ """断开Supabase连接"""
47
+ # Supabase客户端不需要显式断开连接
48
+ self._connected = False
49
+ logger.info("Supabase会话结束")
50
+ return True
51
+
52
+ def is_connected(self) -> bool:
53
+ """检查Supabase连接状态"""
54
+ # Supabase使用REST API,只要有客户端实例就认为是连接的
55
+ return self.connection is not None and self._connected
56
+
57
+ def execute(self, query: str, params: Optional[tuple] = None) -> Any:
58
+ """
59
+ Supabase不直接支持SQL查询
60
+
61
+ Raises:
62
+ NotSupportedError: 总是抛出此异常
63
+ """
64
+ raise NotSupportedError("Supabase使用REST API,请使用insert/update/delete/select方法")
65
+
66
+ def insert(self, table: str, data: Dict[str, Any]) -> Any:
67
+ """插入数据到Supabase"""
68
+ if not self.is_connected():
69
+ raise ConnectionError("数据库未连接")
70
+
71
+ try:
72
+ result = self.connection.table(table).insert(data).execute()
73
+ logger.debug(f"插入数据成功: {result.data}")
74
+ return result.data
75
+
76
+ except Exception as e:
77
+ logger.error(f"插入数据失败: {e}")
78
+ raise QueryError(f"插入数据失败: {e}")
79
+
80
+ def insert_many(self, table: str, data_list: List[Dict[str, Any]]) -> List[Dict]:
81
+ """
82
+ 批量插入数据
83
+
84
+ Args:
85
+ table: 表名
86
+ data_list: 数据列表
87
+
88
+ Returns:
89
+ List[Dict]: 插入的数据
90
+ """
91
+ if not self.is_connected():
92
+ raise ConnectionError("数据库未连接")
93
+
94
+ try:
95
+ result = self.connection.table(table).insert(data_list).execute()
96
+ logger.debug(f"批量插入 {len(result.data)} 条数据")
97
+ return result.data
98
+
99
+ except Exception as e:
100
+ logger.error(f"批量插入数据失败: {e}")
101
+ raise QueryError(f"批量插入数据失败: {e}")
102
+
103
+ def update(self, table: str, data: Dict[str, Any],
104
+ condition: Dict[str, Any]) -> int:
105
+ """更新Supabase数据"""
106
+ if not self.is_connected():
107
+ raise ConnectionError("数据库未连接")
108
+
109
+ try:
110
+ query = self.connection.table(table).update(data)
111
+
112
+ # 添加条件
113
+ for key, value in condition.items():
114
+ query = query.eq(key, value)
115
+
116
+ result = query.execute()
117
+ count = len(result.data)
118
+ logger.debug(f"更新了 {count} 条记录")
119
+ return count
120
+
121
+ except Exception as e:
122
+ logger.error(f"更新数据失败: {e}")
123
+ raise QueryError(f"更新数据失败: {e}")
124
+
125
+ def delete(self, table: str, condition: Dict[str, Any]) -> int:
126
+ """删除Supabase数据"""
127
+ if not self.is_connected():
128
+ raise ConnectionError("数据库未连接")
129
+
130
+ try:
131
+ query = self.connection.table(table).delete()
132
+
133
+ # 添加条件
134
+ for key, value in condition.items():
135
+ query = query.eq(key, value)
136
+
137
+ result = query.execute()
138
+ count = len(result.data)
139
+ logger.debug(f"删除了 {count} 条记录")
140
+ return count
141
+
142
+ except Exception as e:
143
+ logger.error(f"删除数据失败: {e}")
144
+ raise QueryError(f"删除数据失败: {e}")
145
+
146
+ def select(self, table: str,
147
+ fields: Optional[List[str]] = None,
148
+ condition: Optional[Dict[str, Any]] = None,
149
+ limit: Optional[int] = None,
150
+ offset: Optional[int] = None,
151
+ order_by: Optional[List[tuple]] = None) -> List[Dict]:
152
+ """查询Supabase数据"""
153
+ if not self.is_connected():
154
+ raise ConnectionError("数据库未连接")
155
+
156
+ try:
157
+ # 构建字段选择
158
+ fields_str = ','.join(fields) if fields else '*'
159
+ query = self.connection.table(table).select(fields_str)
160
+
161
+ # 添加条件
162
+ if condition:
163
+ for key, value in condition.items():
164
+ query = query.eq(key, value)
165
+
166
+ # 排序
167
+ if order_by:
168
+ for field, direction in order_by:
169
+ desc = direction.upper() == 'DESC'
170
+ query = query.order(field, desc=desc)
171
+
172
+ # 分页
173
+ if offset is not None:
174
+ query = query.range(offset, offset + (limit or 999999))
175
+ elif limit is not None:
176
+ query = query.limit(limit)
177
+
178
+ result = query.execute()
179
+ logger.debug(f"查询到 {len(result.data)} 条记录")
180
+ return result.data
181
+
182
+ except Exception as e:
183
+ logger.error(f"查询数据失败: {e}")
184
+ raise QueryError(f"查询数据失败: {e}")
185
+
186
+ def upsert(self, table: str, data: Union[Dict[str, Any], List[Dict[str, Any]]]) -> Any:
187
+ """
188
+ 插入或更新数据(如果存在则更新)
189
+
190
+ Args:
191
+ table: 表名
192
+ data: 单条或多条数据
193
+
194
+ Returns:
195
+ 插入或更新的数据
196
+ """
197
+ if not self.is_connected():
198
+ raise ConnectionError("数据库未连接")
199
+
200
+ try:
201
+ result = self.connection.table(table).upsert(data).execute()
202
+ logger.debug(f"Upsert操作成功: {result.data}")
203
+ return result.data
204
+
205
+ except Exception as e:
206
+ logger.error(f"Upsert操作失败: {e}")
207
+ raise QueryError(f"Upsert操作失败: {e}")
208
+
209
+ def rpc(self, function_name: str, params: Optional[Dict[str, Any]] = None) -> Any:
210
+ """
211
+ 调用Supabase存储过程
212
+
213
+ Args:
214
+ function_name: 函数名
215
+ params: 参数
216
+
217
+ Returns:
218
+ 函数返回结果
219
+ """
220
+ if not self.is_connected():
221
+ raise ConnectionError("数据库未连接")
222
+
223
+ try:
224
+ result = self.connection.rpc(function_name, params or {}).execute()
225
+ logger.debug(f"调用RPC函数成功: {function_name}")
226
+ return result.data
227
+
228
+ except Exception as e:
229
+ logger.error(f"调用RPC函数失败: {e}")
230
+ raise QueryError(f"调用RPC函数失败: {e}")