ezKit 1.0.0b5__tar.gz → 2.0.14__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.
- ezkit-2.0.14/PKG-INFO +15 -0
- ezkit-2.0.14/README.md +8 -0
- ezkit-2.0.14/ezKit/__init__.py +0 -0
- ezkit-2.0.14/ezKit/cipher.py +82 -0
- ezkit-2.0.14/ezKit/database.py +502 -0
- ezkit-2.0.14/ezKit/http.py +140 -0
- ezkit-2.0.14/ezKit/redix.py +159 -0
- ezkit-2.0.14/ezKit/stock.py +85 -0
- ezkit-2.0.14/ezKit/utils.py +203 -0
- ezkit-2.0.14/ezKit.egg-info/PKG-INFO +15 -0
- {ezKit-1.0.0b5 → ezkit-2.0.14}/ezKit.egg-info/SOURCES.txt +3 -10
- ezkit-2.0.14/ezKit.egg-info/requires.txt +1 -0
- ezkit-2.0.14/pyproject.toml +19 -0
- ezkit-2.0.14/setup.py +16 -0
- ezKit-1.0.0b5/PKG-INFO +0 -8
- ezKit-1.0.0b5/README.md +0 -82
- ezKit-1.0.0b5/ezKit/__init__.py +0 -1
- ezKit-1.0.0b5/ezKit/bottle.py +0 -3809
- ezKit-1.0.0b5/ezKit/cipher.py +0 -74
- ezKit-1.0.0b5/ezKit/database.py +0 -168
- ezKit-1.0.0b5/ezKit/files.py +0 -348
- ezKit-1.0.0b5/ezKit/http.py +0 -92
- ezKit-1.0.0b5/ezKit/mongo.py +0 -62
- ezKit-1.0.0b5/ezKit/plots.py +0 -155
- ezKit-1.0.0b5/ezKit/redis.py +0 -51
- ezKit-1.0.0b5/ezKit/reports.py +0 -274
- ezKit-1.0.0b5/ezKit/sendemail.py +0 -146
- ezKit-1.0.0b5/ezKit/utils.py +0 -1220
- ezKit-1.0.0b5/ezKit/weixin.py +0 -148
- ezKit-1.0.0b5/ezKit/xftp.py +0 -194
- ezKit-1.0.0b5/ezKit/zabbix.py +0 -866
- ezKit-1.0.0b5/ezKit.egg-info/PKG-INFO +0 -8
- ezKit-1.0.0b5/ezKit.egg-info/requires.txt +0 -1
- ezKit-1.0.0b5/setup.py +0 -15
- {ezKit-1.0.0b5 → ezkit-2.0.14}/LICENSE +0 -0
- {ezKit-1.0.0b5 → ezkit-2.0.14}/MANIFEST.in +0 -0
- {ezKit-1.0.0b5 → ezkit-2.0.14}/ezKit.egg-info/dependency_links.txt +0 -0
- {ezKit-1.0.0b5 → ezkit-2.0.14}/ezKit.egg-info/top_level.txt +0 -0
- {ezKit-1.0.0b5 → ezkit-2.0.14}/setup.cfg +0 -0
ezkit-2.0.14/PKG-INFO
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ezKit
|
|
3
|
+
Version: 2.0.14
|
|
4
|
+
Summary: Easy Kit
|
|
5
|
+
Author: septvean
|
|
6
|
+
Author-email: septvean@gmail.com
|
|
7
|
+
Requires-Python: >=3.12
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: loguru>=0.7
|
|
10
|
+
Dynamic: author
|
|
11
|
+
Dynamic: author-email
|
|
12
|
+
Dynamic: license-file
|
|
13
|
+
Dynamic: requires-dist
|
|
14
|
+
Dynamic: requires-python
|
|
15
|
+
Dynamic: summary
|
ezkit-2.0.14/README.md
ADDED
|
File without changes
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from os import environ, urandom
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from cryptography.hazmat.primitives import hashes
|
|
7
|
+
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
|
8
|
+
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
|
|
9
|
+
from loguru import logger
|
|
10
|
+
|
|
11
|
+
DEBUG = environ.get("DEBUG")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class AESCipher:
|
|
16
|
+
"""
|
|
17
|
+
Modern AES Encryption using cryptography (AES-GCM mode).
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
key_string: str
|
|
21
|
+
algo: str = "sha256"
|
|
22
|
+
|
|
23
|
+
def __post_init__(self):
|
|
24
|
+
|
|
25
|
+
# 派生 32 字节 AES 密钥(256bit)
|
|
26
|
+
algo_map = {
|
|
27
|
+
"sha256": hashes.SHA256(),
|
|
28
|
+
"sha384": hashes.SHA384(),
|
|
29
|
+
"sha512": hashes.SHA512(),
|
|
30
|
+
"sha3_256": hashes.SHA3_256(),
|
|
31
|
+
"sha3_512": hashes.SHA3_512(),
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
hash_algo = algo_map.get(self.algo, hashes.SHA256())
|
|
35
|
+
|
|
36
|
+
hkdf = HKDF(
|
|
37
|
+
algorithm=hash_algo,
|
|
38
|
+
length=32, # AES-256
|
|
39
|
+
salt=None,
|
|
40
|
+
info=b"aes-gcm-key-derivation",
|
|
41
|
+
)
|
|
42
|
+
self.key = hkdf.derive(self.key_string.encode())
|
|
43
|
+
self.aesgcm = AESGCM(self.key)
|
|
44
|
+
|
|
45
|
+
def encrypt(self, text: str, aad: Optional[bytes] = None) -> str:
|
|
46
|
+
try:
|
|
47
|
+
nonce = urandom(12) # 推荐 12 字节
|
|
48
|
+
ciphertext = self.aesgcm.encrypt(nonce, text.encode(), aad)
|
|
49
|
+
# 输出格式: nonce + ciphertext
|
|
50
|
+
return base64.b64encode(nonce + ciphertext).decode()
|
|
51
|
+
except Exception as e:
|
|
52
|
+
logger.error(f"AES encrypt error: {e}")
|
|
53
|
+
if DEBUG:
|
|
54
|
+
logger.exception(e)
|
|
55
|
+
else:
|
|
56
|
+
logger.error(e)
|
|
57
|
+
return ""
|
|
58
|
+
|
|
59
|
+
def decrypt(self, token: str, aad: Optional[bytes] = None) -> str:
|
|
60
|
+
try:
|
|
61
|
+
data = base64.b64decode(token)
|
|
62
|
+
nonce = data[:12]
|
|
63
|
+
ciphertext = data[12:]
|
|
64
|
+
plaintext = self.aesgcm.decrypt(nonce, ciphertext, aad)
|
|
65
|
+
return plaintext.decode()
|
|
66
|
+
except Exception as e:
|
|
67
|
+
logger.error(f"AES decrypt error: {e}")
|
|
68
|
+
if DEBUG:
|
|
69
|
+
logger.exception(e)
|
|
70
|
+
else:
|
|
71
|
+
logger.error(e)
|
|
72
|
+
return ""
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# 测试
|
|
76
|
+
# if __name__ == "__main__":
|
|
77
|
+
# aes = AESCipher("vB7DoRm9C2Kd", algo="sha256")
|
|
78
|
+
# text_instance = {"info": "Hello World"}
|
|
79
|
+
# token_instance = aes.encrypt(json.dumps(text_instance))
|
|
80
|
+
# print(token_instance)
|
|
81
|
+
# dec = aes.decrypt(token_instance)
|
|
82
|
+
# print(dec)
|
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
"""Database Library"""
|
|
2
|
+
|
|
3
|
+
from os import environ
|
|
4
|
+
|
|
5
|
+
from loguru import logger
|
|
6
|
+
from sqlalchemy import ColumnElement, TextClause, and_, delete, text, update
|
|
7
|
+
from sqlalchemy.engine import CursorResult, Result, ScalarResult
|
|
8
|
+
from sqlalchemy.exc import IntegrityError
|
|
9
|
+
from sqlalchemy.orm import DeclarativeBase, Session
|
|
10
|
+
from sqlalchemy.sql import Delete, Insert, Select, Update
|
|
11
|
+
|
|
12
|
+
# --------------------------------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
DEBUG = environ.get("DEBUG")
|
|
15
|
+
|
|
16
|
+
# --------------------------------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# 同步
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def session_execute(session: Session, statement: str | TextClause | Delete | Insert | Select | Update) -> Result | CursorResult | None:
|
|
23
|
+
"""Session execute statement"""
|
|
24
|
+
|
|
25
|
+
# SQL 语句既可以是 text() 对象 也可以是 ORM 构建的 statement 对象
|
|
26
|
+
#
|
|
27
|
+
# text() 对象可以是任意 SQL 语句
|
|
28
|
+
# statement 对象包括 select, insert, update, delete 等
|
|
29
|
+
#
|
|
30
|
+
# 原生 SQL 返回 Result
|
|
31
|
+
# ORM 返回 CursorResult
|
|
32
|
+
|
|
33
|
+
# 执行语句
|
|
34
|
+
info: str = "database execute statement"
|
|
35
|
+
|
|
36
|
+
if DEBUG:
|
|
37
|
+
logger.debug(f"{info} [SQL]\n------\n\n{statement}\n\n------")
|
|
38
|
+
|
|
39
|
+
logger.info(f"{info} [start]")
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
stmt = text(statement) if isinstance(statement, str) else statement
|
|
43
|
+
result: Result = session.execute(stmt)
|
|
44
|
+
session.commit()
|
|
45
|
+
logger.success(f"{info} [success]")
|
|
46
|
+
return result
|
|
47
|
+
except Exception as e:
|
|
48
|
+
logger.error(f"{info} [error]")
|
|
49
|
+
session.rollback()
|
|
50
|
+
if DEBUG:
|
|
51
|
+
logger.exception(e)
|
|
52
|
+
else:
|
|
53
|
+
logger.error(e)
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def session_scalars(session: Session, statement: str | TextClause | Delete | Insert | Select | Update) -> ScalarResult | None:
|
|
58
|
+
"""Session scalars statement"""
|
|
59
|
+
|
|
60
|
+
# SQL 语句既可以是 text() 对象 也可以是 ORM 构建的 statement 对象
|
|
61
|
+
#
|
|
62
|
+
# text() 对象可以是任意 SQL 语句
|
|
63
|
+
# statement 对象包括 select, insert, update, delete 等
|
|
64
|
+
|
|
65
|
+
# 示例:
|
|
66
|
+
#
|
|
67
|
+
# 查询出所有 code (只有一列, 所以这里使用 scalars(), 返回的是 list 而不是 list[tuple])
|
|
68
|
+
#
|
|
69
|
+
# sql: str = text("SELECT code FROM ashare;")
|
|
70
|
+
# records = session_execute.execute(sql).scalars().all()
|
|
71
|
+
#
|
|
72
|
+
# records 是包含所有 code 的列表
|
|
73
|
+
|
|
74
|
+
# 执行语句
|
|
75
|
+
info: str = "database execute statement"
|
|
76
|
+
|
|
77
|
+
if DEBUG:
|
|
78
|
+
logger.debug(f"{info} [SQL]\n------\n\n{statement}\n\n------")
|
|
79
|
+
|
|
80
|
+
logger.info(f"{info} [start]")
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
stmt = text(statement) if isinstance(statement, str) else statement
|
|
84
|
+
result: ScalarResult = session.scalars(stmt)
|
|
85
|
+
session.commit()
|
|
86
|
+
logger.success(f"{info} [success]")
|
|
87
|
+
return result
|
|
88
|
+
except Exception as e:
|
|
89
|
+
logger.error(f"{info} [error]")
|
|
90
|
+
session.rollback()
|
|
91
|
+
if DEBUG:
|
|
92
|
+
logger.exception(e)
|
|
93
|
+
else:
|
|
94
|
+
logger.error(e)
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# --------------------------------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class ORMBase(DeclarativeBase):
|
|
102
|
+
"""ORM Base"""
|
|
103
|
+
|
|
104
|
+
pass # pylint: disable=unnecessary-pass
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class ORMSession:
|
|
108
|
+
"""ORM Session"""
|
|
109
|
+
|
|
110
|
+
# 用 ORM 的主要目的是简化数据库操作
|
|
111
|
+
# 用于处理 dict 类型的数据, 而不是 ORM 对象类型的数据
|
|
112
|
+
#
|
|
113
|
+
# 例如:
|
|
114
|
+
#
|
|
115
|
+
# 插入 dict 或 list[dict]
|
|
116
|
+
# 根据 dict 查询, 删除, 更新数据
|
|
117
|
+
|
|
118
|
+
# 初始化
|
|
119
|
+
def __init__(self, model: type[ORMBase], session: Session):
|
|
120
|
+
|
|
121
|
+
# 模型类
|
|
122
|
+
self.model: type[ORMBase] = model
|
|
123
|
+
self.session: Session = session
|
|
124
|
+
self.info: str = f"Model: {model.__tablename__}"
|
|
125
|
+
|
|
126
|
+
# ----------------------------------------------------------------------------------------------
|
|
127
|
+
|
|
128
|
+
# 关闭连接
|
|
129
|
+
def close(self):
|
|
130
|
+
"关闭连接"
|
|
131
|
+
# self.session.get_bind().dispose()
|
|
132
|
+
bind = self.session.get_bind()
|
|
133
|
+
if hasattr(bind, "dispose"):
|
|
134
|
+
bind.dispose() # type: ignore
|
|
135
|
+
self.session.close()
|
|
136
|
+
|
|
137
|
+
# ----------------------------------------------------------------------------------------------
|
|
138
|
+
|
|
139
|
+
# 连接测试
|
|
140
|
+
def test_connection(self) -> bool:
|
|
141
|
+
"""测试数据库连接"""
|
|
142
|
+
|
|
143
|
+
# 测试连接 | 开始
|
|
144
|
+
logger.info(f"{self.info} [ test connection | start ]")
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
|
|
148
|
+
self.session.execute(text("SELECT 1;"))
|
|
149
|
+
|
|
150
|
+
# 测试连接 | 成功
|
|
151
|
+
logger.success(f"{self.info} [ test connection | success ]")
|
|
152
|
+
|
|
153
|
+
return True
|
|
154
|
+
|
|
155
|
+
except Exception as e:
|
|
156
|
+
|
|
157
|
+
# 测试连接 | 错误
|
|
158
|
+
logger.error(f"{self.info} [ test connection | error ]")
|
|
159
|
+
|
|
160
|
+
if DEBUG:
|
|
161
|
+
logger.exception(e)
|
|
162
|
+
else:
|
|
163
|
+
logger.error(e)
|
|
164
|
+
|
|
165
|
+
return False
|
|
166
|
+
|
|
167
|
+
# ----------------------------------------------------------------------------------------------
|
|
168
|
+
|
|
169
|
+
# 初始化 model 表
|
|
170
|
+
def init_model(self) -> bool:
|
|
171
|
+
"""初始化模型表, 删除已存在的表并重新创建"""
|
|
172
|
+
|
|
173
|
+
# 初始化 | 开始
|
|
174
|
+
logger.info(f"{self.info} [ initialization | start ]")
|
|
175
|
+
|
|
176
|
+
try:
|
|
177
|
+
|
|
178
|
+
self.model.__table__.drop(bind=self.session.get_bind(), checkfirst=True) # type: ignore
|
|
179
|
+
self.model.__table__.create(bind=self.session.get_bind(), checkfirst=True) # type: ignore
|
|
180
|
+
|
|
181
|
+
# 初始化 | 成功
|
|
182
|
+
logger.success(f"{self.info} [ initialization | success ]")
|
|
183
|
+
|
|
184
|
+
return True
|
|
185
|
+
|
|
186
|
+
except Exception as e:
|
|
187
|
+
|
|
188
|
+
# 初始化 | 错误
|
|
189
|
+
logger.error(f"{self.info} [ initialization | error ]")
|
|
190
|
+
|
|
191
|
+
if DEBUG:
|
|
192
|
+
logger.exception(e)
|
|
193
|
+
else:
|
|
194
|
+
logger.error(e)
|
|
195
|
+
|
|
196
|
+
return False
|
|
197
|
+
|
|
198
|
+
# ----------------------------------------------------------------------------------------------
|
|
199
|
+
|
|
200
|
+
# 执行 SQL 语句
|
|
201
|
+
|
|
202
|
+
def execute(self, *args, **kwargs):
|
|
203
|
+
"""执行语句"""
|
|
204
|
+
return session_execute(self.session, *args, **kwargs)
|
|
205
|
+
|
|
206
|
+
def scalars(self, *args, **kwargs):
|
|
207
|
+
"""执行 SQL 语句"""
|
|
208
|
+
return session_scalars(self.session, *args, **kwargs)
|
|
209
|
+
|
|
210
|
+
# ----------------------------------------------------------------------------------------------
|
|
211
|
+
|
|
212
|
+
# 插入
|
|
213
|
+
def create(self, data: dict | list[dict] | tuple[dict, ...], bulk: bool = False) -> int:
|
|
214
|
+
"""插入数据, 支持单条和批量插入"""
|
|
215
|
+
|
|
216
|
+
# 数据格式:
|
|
217
|
+
#
|
|
218
|
+
# 单条: dict
|
|
219
|
+
# 批量: list[dict] | tuple[dict]
|
|
220
|
+
#
|
|
221
|
+
# 规范: 只接受以上两种形式的数据
|
|
222
|
+
#
|
|
223
|
+
# dict 的 key 必须和 模型(model) 的字段名一致
|
|
224
|
+
#
|
|
225
|
+
# 纯 list 或者 tuple 需要转换为 list[dict] 或者 tuple[dict] 格式
|
|
226
|
+
#
|
|
227
|
+
# 如果 bulk 为 True, 则使用 session.bulk_insert_mappings, 否则使用 session.add_all
|
|
228
|
+
#
|
|
229
|
+
# bulk_insert_mappings 适合批量插入数据, 适用于数据量较大的情况, 速度更快
|
|
230
|
+
#
|
|
231
|
+
# 注意: bulk_insert_mappings 只接受 list[dict] 格式数据
|
|
232
|
+
#
|
|
233
|
+
# 返回插入的记录数
|
|
234
|
+
|
|
235
|
+
# 单条插入
|
|
236
|
+
if isinstance(data, dict) and data:
|
|
237
|
+
|
|
238
|
+
# 创建单条数据 | 开始
|
|
239
|
+
logger.info(f"{self.info} [ create single data | start ]")
|
|
240
|
+
|
|
241
|
+
try:
|
|
242
|
+
|
|
243
|
+
# self.session.execute(
|
|
244
|
+
# insert(self.model.__table__).values(data)
|
|
245
|
+
# )
|
|
246
|
+
|
|
247
|
+
self.session.add(self.model(**data))
|
|
248
|
+
self.session.commit()
|
|
249
|
+
|
|
250
|
+
# 创建单条数据 | 成功
|
|
251
|
+
logger.success(f"{self.info} [ create single data | success ]")
|
|
252
|
+
|
|
253
|
+
return 1
|
|
254
|
+
|
|
255
|
+
except Exception as e:
|
|
256
|
+
|
|
257
|
+
# 创建单条数据 | 错误
|
|
258
|
+
logger.error(f"{self.info} [ create single data | error ]")
|
|
259
|
+
|
|
260
|
+
self.session.rollback()
|
|
261
|
+
|
|
262
|
+
if DEBUG:
|
|
263
|
+
logger.exception(e)
|
|
264
|
+
else:
|
|
265
|
+
logger.error(e)
|
|
266
|
+
|
|
267
|
+
return 0
|
|
268
|
+
|
|
269
|
+
# 批量插入
|
|
270
|
+
if isinstance(data, list | tuple) and data:
|
|
271
|
+
|
|
272
|
+
# 创建批量数据 | 开始
|
|
273
|
+
logger.info(f"{self.info} [ create batch data | start ]")
|
|
274
|
+
|
|
275
|
+
try:
|
|
276
|
+
|
|
277
|
+
if bulk:
|
|
278
|
+
self.session.bulk_insert_mappings(self.model.__mapper__, data)
|
|
279
|
+
else:
|
|
280
|
+
orm_instances = [self.model(**item) for item in data]
|
|
281
|
+
self.session.add_all(orm_instances)
|
|
282
|
+
|
|
283
|
+
self.session.commit()
|
|
284
|
+
|
|
285
|
+
# 创建批量数据 | 成功
|
|
286
|
+
logger.success(f"{self.info} [ create batch data | success ]")
|
|
287
|
+
|
|
288
|
+
return len(data)
|
|
289
|
+
|
|
290
|
+
except IntegrityError as ie:
|
|
291
|
+
|
|
292
|
+
# 批量插入遇到重复或约束冲突, 回退并逐条插入.
|
|
293
|
+
|
|
294
|
+
# 创建批量数据 | 错误 | 尝试逐条创建
|
|
295
|
+
logger.error(f"{self.info} [ create batch data | error ]")
|
|
296
|
+
|
|
297
|
+
self.session.rollback()
|
|
298
|
+
|
|
299
|
+
logger.warning(ie)
|
|
300
|
+
|
|
301
|
+
count = 0
|
|
302
|
+
|
|
303
|
+
for item in data:
|
|
304
|
+
|
|
305
|
+
try:
|
|
306
|
+
|
|
307
|
+
self.session.add(self.model(**item))
|
|
308
|
+
self.session.commit()
|
|
309
|
+
|
|
310
|
+
count += 1
|
|
311
|
+
|
|
312
|
+
# 已插入 count 条数据
|
|
313
|
+
logger.success(f"{self.info} [ {count} pieces of data have been created ]")
|
|
314
|
+
|
|
315
|
+
except IntegrityError:
|
|
316
|
+
|
|
317
|
+
# 数据已存在 | 跳过
|
|
318
|
+
logger.warning(f"{self.info} [ data already exists | skip ]")
|
|
319
|
+
|
|
320
|
+
self.session.rollback()
|
|
321
|
+
|
|
322
|
+
except Exception as e:
|
|
323
|
+
|
|
324
|
+
# 逐条插入时发生错误, 插入数据|失败|跳过该条数据
|
|
325
|
+
logger.error(f"{self.info} [ create data one by one | error ]")
|
|
326
|
+
|
|
327
|
+
self.session.rollback()
|
|
328
|
+
|
|
329
|
+
if DEBUG:
|
|
330
|
+
logger.exception(e)
|
|
331
|
+
else:
|
|
332
|
+
logger.error(e)
|
|
333
|
+
|
|
334
|
+
# 逐条插入数据 | 完成
|
|
335
|
+
logger.success(f"{self.info} [ create data one by one | finish ]")
|
|
336
|
+
|
|
337
|
+
return count
|
|
338
|
+
|
|
339
|
+
# 插入数据|空
|
|
340
|
+
logger.warning(f"{self.info} [ create data | none ]")
|
|
341
|
+
|
|
342
|
+
return 0
|
|
343
|
+
|
|
344
|
+
# ----------------------------------------------------------------------------------------------
|
|
345
|
+
|
|
346
|
+
def _build_where(self, where: dict | list | tuple | ColumnElement | None) -> ColumnElement | None:
|
|
347
|
+
"""build where"""
|
|
348
|
+
|
|
349
|
+
# where 支持:
|
|
350
|
+
|
|
351
|
+
# dict: {"name": "Tom", "status": 1}
|
|
352
|
+
# list/tuple: [model.age > 18, model.status == 1]
|
|
353
|
+
# 单个表达式: model.age > 18
|
|
354
|
+
|
|
355
|
+
try:
|
|
356
|
+
|
|
357
|
+
if where is None:
|
|
358
|
+
return None
|
|
359
|
+
|
|
360
|
+
# dict 转表达式
|
|
361
|
+
if isinstance(where, dict) and where:
|
|
362
|
+
|
|
363
|
+
conditions = []
|
|
364
|
+
|
|
365
|
+
for k, v in where.items():
|
|
366
|
+
column = getattr(self.model, k)
|
|
367
|
+
conditions.append(column == v)
|
|
368
|
+
|
|
369
|
+
return and_(*conditions)
|
|
370
|
+
|
|
371
|
+
# where 是 list/tuple,或表达式
|
|
372
|
+
if isinstance(where, (list, tuple)) and where:
|
|
373
|
+
return and_(*where)
|
|
374
|
+
|
|
375
|
+
if isinstance(where, ColumnElement):
|
|
376
|
+
# ORM 表达式
|
|
377
|
+
# 单个表达式
|
|
378
|
+
return where
|
|
379
|
+
|
|
380
|
+
return None
|
|
381
|
+
|
|
382
|
+
except Exception as e:
|
|
383
|
+
if DEBUG:
|
|
384
|
+
logger.exception(e)
|
|
385
|
+
else:
|
|
386
|
+
logger.error(e)
|
|
387
|
+
return None
|
|
388
|
+
|
|
389
|
+
# ----------------------------------------------------------------------------------------------
|
|
390
|
+
|
|
391
|
+
# 更新
|
|
392
|
+
def update(self, where: dict | list | tuple | ColumnElement | None, data: dict) -> int:
|
|
393
|
+
"""Update"""
|
|
394
|
+
|
|
395
|
+
# where 可以是:
|
|
396
|
+
#
|
|
397
|
+
# {"id": 1}
|
|
398
|
+
# {"name": "Tom", "status": 1}
|
|
399
|
+
# [model.age > 18, model.status == 1]
|
|
400
|
+
# model.age > 18
|
|
401
|
+
#
|
|
402
|
+
# 示例:
|
|
403
|
+
#
|
|
404
|
+
# crud.update({"id": 5}, {"name": "NewName"})
|
|
405
|
+
# crud.update({"name": "Tom"}, {"age": 20})
|
|
406
|
+
# crud.update([User.age > 18, User.status == 1], {"level": 2})
|
|
407
|
+
# crud.update(User.age < 10, {"flag": False})
|
|
408
|
+
|
|
409
|
+
# 更新数据 | 开始
|
|
410
|
+
logger.info(f"{self.info} [ update data | start ]")
|
|
411
|
+
|
|
412
|
+
try:
|
|
413
|
+
|
|
414
|
+
cond = self._build_where(where)
|
|
415
|
+
|
|
416
|
+
if cond is None:
|
|
417
|
+
# 创建 where 语句错误
|
|
418
|
+
logger.error(f"{self.info} [ build where error ]")
|
|
419
|
+
return 0
|
|
420
|
+
|
|
421
|
+
stmt = update(self.model).where(cond).values(**data).execution_options(synchronize_session="fetch")
|
|
422
|
+
|
|
423
|
+
# 因为 Update 是通过操作 ORM, 所以这里返回的是 CursorResult
|
|
424
|
+
result: CursorResult | None = self.execute(stmt) # type: ignore
|
|
425
|
+
|
|
426
|
+
if not result:
|
|
427
|
+
|
|
428
|
+
# 更新数据 | 错误
|
|
429
|
+
logger.error(f"{self.info} [ update data | error ]")
|
|
430
|
+
|
|
431
|
+
return 0
|
|
432
|
+
|
|
433
|
+
# 更新数据 | 成功
|
|
434
|
+
logger.success(f"{self.info} [ update data | success ]")
|
|
435
|
+
|
|
436
|
+
return result.rowcount
|
|
437
|
+
|
|
438
|
+
except Exception as e:
|
|
439
|
+
|
|
440
|
+
# 更新数据 | 错误
|
|
441
|
+
logger.error(f"{self.info} [ update data | error ]")
|
|
442
|
+
|
|
443
|
+
if DEBUG:
|
|
444
|
+
logger.exception(e)
|
|
445
|
+
else:
|
|
446
|
+
logger.error(e)
|
|
447
|
+
|
|
448
|
+
return 0
|
|
449
|
+
|
|
450
|
+
# ----------------------------------------------------------------------------------------------
|
|
451
|
+
|
|
452
|
+
# 删除
|
|
453
|
+
def delete(self, where: dict | list | tuple | ColumnElement | None) -> int:
|
|
454
|
+
"""Delete"""
|
|
455
|
+
|
|
456
|
+
# 示例
|
|
457
|
+
#
|
|
458
|
+
# crud.delete({"id": 3})
|
|
459
|
+
# crud.delete({"status": 0})
|
|
460
|
+
# crud.delete([User.age < 8])
|
|
461
|
+
# crud.delete(User.age < 8)
|
|
462
|
+
|
|
463
|
+
# 删除数据 | 开始
|
|
464
|
+
logger.info(f"{self.info} [ delete data | start ]")
|
|
465
|
+
|
|
466
|
+
try:
|
|
467
|
+
|
|
468
|
+
cond = self._build_where(where)
|
|
469
|
+
|
|
470
|
+
if cond is None:
|
|
471
|
+
# 创建 where 语句错误
|
|
472
|
+
logger.error(f"{self.info} [ build where error ]")
|
|
473
|
+
return 0
|
|
474
|
+
|
|
475
|
+
stmt = delete(self.model).where(cond)
|
|
476
|
+
|
|
477
|
+
# 因为 Delete 是通过操作 ORM, 所以这里返回的是 CursorResult
|
|
478
|
+
result: CursorResult | None = self.execute(stmt) # type: ignore
|
|
479
|
+
|
|
480
|
+
if not result:
|
|
481
|
+
|
|
482
|
+
# 删除数据 | 错误
|
|
483
|
+
logger.error(f"{self.info} [ delete data | error ]")
|
|
484
|
+
|
|
485
|
+
return 0
|
|
486
|
+
|
|
487
|
+
# 删除数据 | 成功
|
|
488
|
+
logger.success(f"{self.info} [ delete data | success ]")
|
|
489
|
+
|
|
490
|
+
return result.rowcount
|
|
491
|
+
|
|
492
|
+
except Exception as e:
|
|
493
|
+
|
|
494
|
+
# 删除数据 | 错误
|
|
495
|
+
logger.error(f"{self.info} [ delete data | error ]")
|
|
496
|
+
|
|
497
|
+
if DEBUG:
|
|
498
|
+
logger.exception(e)
|
|
499
|
+
else:
|
|
500
|
+
logger.error(e)
|
|
501
|
+
|
|
502
|
+
return 0
|