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.
Files changed (39) hide show
  1. ezkit-2.0.14/PKG-INFO +15 -0
  2. ezkit-2.0.14/README.md +8 -0
  3. ezkit-2.0.14/ezKit/__init__.py +0 -0
  4. ezkit-2.0.14/ezKit/cipher.py +82 -0
  5. ezkit-2.0.14/ezKit/database.py +502 -0
  6. ezkit-2.0.14/ezKit/http.py +140 -0
  7. ezkit-2.0.14/ezKit/redix.py +159 -0
  8. ezkit-2.0.14/ezKit/stock.py +85 -0
  9. ezkit-2.0.14/ezKit/utils.py +203 -0
  10. ezkit-2.0.14/ezKit.egg-info/PKG-INFO +15 -0
  11. {ezKit-1.0.0b5 → ezkit-2.0.14}/ezKit.egg-info/SOURCES.txt +3 -10
  12. ezkit-2.0.14/ezKit.egg-info/requires.txt +1 -0
  13. ezkit-2.0.14/pyproject.toml +19 -0
  14. ezkit-2.0.14/setup.py +16 -0
  15. ezKit-1.0.0b5/PKG-INFO +0 -8
  16. ezKit-1.0.0b5/README.md +0 -82
  17. ezKit-1.0.0b5/ezKit/__init__.py +0 -1
  18. ezKit-1.0.0b5/ezKit/bottle.py +0 -3809
  19. ezKit-1.0.0b5/ezKit/cipher.py +0 -74
  20. ezKit-1.0.0b5/ezKit/database.py +0 -168
  21. ezKit-1.0.0b5/ezKit/files.py +0 -348
  22. ezKit-1.0.0b5/ezKit/http.py +0 -92
  23. ezKit-1.0.0b5/ezKit/mongo.py +0 -62
  24. ezKit-1.0.0b5/ezKit/plots.py +0 -155
  25. ezKit-1.0.0b5/ezKit/redis.py +0 -51
  26. ezKit-1.0.0b5/ezKit/reports.py +0 -274
  27. ezKit-1.0.0b5/ezKit/sendemail.py +0 -146
  28. ezKit-1.0.0b5/ezKit/utils.py +0 -1220
  29. ezKit-1.0.0b5/ezKit/weixin.py +0 -148
  30. ezKit-1.0.0b5/ezKit/xftp.py +0 -194
  31. ezKit-1.0.0b5/ezKit/zabbix.py +0 -866
  32. ezKit-1.0.0b5/ezKit.egg-info/PKG-INFO +0 -8
  33. ezKit-1.0.0b5/ezKit.egg-info/requires.txt +0 -1
  34. ezKit-1.0.0b5/setup.py +0 -15
  35. {ezKit-1.0.0b5 → ezkit-2.0.14}/LICENSE +0 -0
  36. {ezKit-1.0.0b5 → ezkit-2.0.14}/MANIFEST.in +0 -0
  37. {ezKit-1.0.0b5 → ezkit-2.0.14}/ezKit.egg-info/dependency_links.txt +0 -0
  38. {ezKit-1.0.0b5 → ezkit-2.0.14}/ezKit.egg-info/top_level.txt +0 -0
  39. {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
@@ -0,0 +1,8 @@
1
+ # Python Easy Kit
2
+
3
+ Command:
4
+
5
+ ```sh
6
+ # Install or Update
7
+ pip install -i https://pypi.org/simple --trusted-host pypi.org -U ezKit
8
+ ```
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