ezKit 1.10.5__tar.gz → 1.10.6__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ezKit
3
- Version: 1.10.5
3
+ Version: 1.10.6
4
4
  Summary: Easy Kit
5
5
  Author: septvean
6
6
  Author-email: septvean@gmail.com
@@ -0,0 +1,363 @@
1
+ """Database"""
2
+ # Column, Table, MetaData API
3
+ # https://docs.sqlalchemy.org/en/14/core/metadata.html#column-table-metadata-api
4
+ # CursorResult
5
+ # https://docs.sqlalchemy.org/en/20/core/connections.html#sqlalchemy.engine.CursorResult
6
+ # PostgreSQL 14 Data Types
7
+ # https://www.postgresql.org/docs/14/datatype.html
8
+ import csv
9
+ import json
10
+ from typing import Any
11
+
12
+ import pandas as pd
13
+ from loguru import logger
14
+ from sqlalchemy import CursorResult, Index, create_engine, text
15
+ from sqlalchemy.orm import DeclarativeBase
16
+
17
+ from . import utils
18
+
19
+
20
+ class Database():
21
+ """Database"""
22
+
23
+ engine = create_engine('sqlite://')
24
+
25
+ def __init__(self, target: str | None = None, **options):
26
+ """Initiation"""
27
+ if isinstance(target, str) and utils.isTrue(target, str):
28
+ if utils.isTrue(options, dict):
29
+ self.engine = create_engine(target, **options)
30
+ else:
31
+ self.engine = create_engine(target)
32
+ else:
33
+ pass
34
+
35
+ # ----------------------------------------------------------------------------------------------
36
+
37
+ def initializer(self):
38
+ """ensure the parent proc's database connections are not touched in the new connection pool"""
39
+ self.engine.dispose(close=False)
40
+
41
+ # ----------------------------------------------------------------------------------------------
42
+
43
+ def connect_test(self) -> bool:
44
+ info = "Database connect test"
45
+ try:
46
+ logger.info(f"{info} ......")
47
+ self.engine.connect()
48
+ logger.success(f"{info} [success]")
49
+ return True
50
+ except Exception as e:
51
+ logger.error(f"{info} [failure]")
52
+ logger.exception(e)
53
+ return False
54
+
55
+ # ----------------------------------------------------------------------------------------------
56
+
57
+ def metadata_init(self, base: DeclarativeBase, **kwargs) -> bool:
58
+ # https://stackoverflow.com/questions/19175311/how-to-create-only-one-table-with-sqlalchemy
59
+ info = "Database init table"
60
+ try:
61
+ logger.info(f"{info} ......")
62
+ base.metadata.drop_all(self.engine, **kwargs)
63
+ base.metadata.create_all(self.engine, **kwargs)
64
+ logger.success(f"{info} [success]")
65
+ return True
66
+ except Exception as e:
67
+ logger.error(f"{info} [failure]")
68
+ logger.exception(e)
69
+ return False
70
+
71
+ # ----------------------------------------------------------------------------------------------
72
+
73
+ def create_index(self, index_name, table_field) -> bool:
74
+ # 创建索引
75
+ # https://stackoverflow.com/a/41254430
76
+ # 示例:
77
+ # index_name: a_share_list_code_idx1
78
+ # table_field: Table_a_share_list.code
79
+ info = "Database create index"
80
+ try:
81
+ logger.info(f"{info} ......")
82
+ idx = Index(index_name, table_field)
83
+ try:
84
+ idx.drop(bind=self.engine)
85
+ except Exception as e:
86
+ logger.exception(e)
87
+ idx.create(bind=self.engine)
88
+ logger.success(f'{info} [success]')
89
+ return True
90
+ except Exception as e:
91
+ logger.error(f'{info} [failure]')
92
+ logger.error(e)
93
+ return False
94
+
95
+ # ----------------------------------------------------------------------------------------------
96
+
97
+ # 私有函数, 保存 execute 的结果到 CSV 文件
98
+ def _result_save(self, file, data) -> bool:
99
+ try:
100
+ outcsv = csv.writer(file)
101
+ outcsv.writerow(data.keys())
102
+ outcsv.writerows(data)
103
+ logger.success("save to csv success")
104
+ return True
105
+ except Exception as e:
106
+ logger.error("save to csv failed")
107
+ logger.exception(e)
108
+ return False
109
+
110
+ # ----------------------------------------------------------------------------------------------
111
+
112
+ # def execute(
113
+ # self,
114
+ # sql: str | None = None,
115
+ # sql_file: str | None = None,
116
+ # sql_file_kwargs: dict | None = None,
117
+ # csv_file: str | None = None,
118
+ # csv_file_kwargs: dict | None = None
119
+ # ) -> CursorResult[Any] | bool:
120
+ # """"运行"""
121
+
122
+ # # ------------------------------------------------------------
123
+
124
+ # # 提取 SQL
125
+ # # 如果 sql 和 sql_file 同时存在, 优先执行 sql
126
+
127
+ # sql_object = None
128
+
129
+ # info: str = f"""Extract SQL: {sql}"""
130
+
131
+ # try:
132
+
133
+ # logger.info(f"{info} ......")
134
+
135
+ # if utils.isTrue(sql, str):
136
+
137
+ # sql_object = sql
138
+
139
+ # elif sql_file is not None and utils.isTrue(sql_file, str):
140
+
141
+ # # 判断文件是否存在
142
+ # if isinstance(sql_file, str) and utils.check_file_type(sql_file, "file") is False:
143
+
144
+ # logger.error(f"No such file: {sql_file}")
145
+ # return False
146
+
147
+ # if isinstance(sql_file, str) and utils.isTrue(sql_file, str):
148
+
149
+ # # 读取文件内容
150
+ # if sql_file_kwargs is not None and utils.isTrue(sql_file_kwargs, dict):
151
+ # with open(sql_file, "r", encoding="utf-8", **sql_file_kwargs) as _file:
152
+ # sql_object = _file.read()
153
+ # else:
154
+ # with open(sql_file, "r", encoding="utf-8") as _file:
155
+ # sql_object = _file.read()
156
+
157
+ # else:
158
+
159
+ # logger.error("SQL or SQL file error")
160
+ # logger.error(f"{info} [failure]")
161
+ # return False
162
+
163
+ # logger.success(f'{info} [success]')
164
+
165
+ # except Exception as e:
166
+
167
+ # logger.error(f"{info} [failure]")
168
+ # logger.exception(e)
169
+ # return False
170
+
171
+ # # ------------------------------------------------------------
172
+
173
+ # # 执行 SQL
174
+
175
+ # info = f"""Execute SQL: {sql_object}"""
176
+
177
+ # try:
178
+
179
+ # logger.info(f"{info} ......")
180
+
181
+ # with self.engine.connect() as connect:
182
+
183
+ # # 执行SQL
184
+ # if sql_object is None:
185
+ # return False
186
+
187
+ # result = connect.execute(text(sql_object))
188
+
189
+ # connect.commit()
190
+
191
+ # if csv_file is None:
192
+ # # 如果 csv_file 没有定义, 则直接返回结果
193
+ # logger.success(f'{info} [success]')
194
+ # return result
195
+
196
+ # # 如果 csv_file 有定义, 则保存结果到 csv_file
197
+ # info_of_save = f"Save result to file: {csv_file}"
198
+ # logger.info(f"{info_of_save} .......")
199
+
200
+ # # 保存结果
201
+ # if isinstance(csv_file_kwargs, dict) and utils.isTrue(csv_file_kwargs, dict):
202
+ # with open(csv_file, "w", encoding="utf-8", **csv_file_kwargs) as _file:
203
+ # result_of_save = self._result_save(_file, result)
204
+ # else:
205
+ # with open(csv_file, "w", encoding="utf-8") as _file:
206
+ # result_of_save = self._result_save(_file, result)
207
+
208
+ # # 检查保存结果
209
+ # if result_of_save is True:
210
+ # logger.success(f'{info_of_save} [success]')
211
+ # logger.success(f'{info} [success]')
212
+ # return True
213
+
214
+ # logger.error(f"{info_of_save} [failure]")
215
+ # logger.error(f"{info} [failure]")
216
+ # return False
217
+
218
+ # except Exception as e:
219
+
220
+ # logger.error(f'{info} [failure]')
221
+ # logger.exception(e)
222
+ # return False
223
+
224
+ # ----------------------------------------------------------------------------------------------
225
+
226
+ def connect_execute(
227
+ self,
228
+ sql: str | None = None,
229
+ read_sql_file: dict | None = None,
230
+ save_to_csv: dict | None = None
231
+ ) -> CursorResult[Any] | bool | None:
232
+
233
+ info: str = 'Database connect execute'
234
+
235
+ logger.info(f"{info} ......")
236
+
237
+ sql_statement: str = ""
238
+
239
+ # ------------------------------------------------------------------------------------------
240
+
241
+ try:
242
+ # SQL文件优先
243
+ if isinstance(read_sql_file, dict) and utils.isTrue(read_sql_file, dict):
244
+ read_sql_file.pop("encoding")
245
+ read_sql_file_kwargs: dict = {
246
+ "mode": "r",
247
+ "encoding": "utf-8",
248
+ **read_sql_file
249
+ }
250
+ with open(encoding="utf-8", **read_sql_file_kwargs) as _file:
251
+ sql_statement = _file.read()
252
+ else:
253
+ if not (isinstance(sql, str) and utils.check_arguments([(sql, str, "sql")])):
254
+ return None
255
+ sql_statement = sql
256
+ except Exception as e:
257
+ logger.exception(e)
258
+ return None
259
+
260
+ # ------------------------------------------------------------------------------------------
261
+
262
+ if not self.connect_test():
263
+ return None
264
+
265
+ # ------------------------------------------------------------------------------------------
266
+
267
+ # 创建一个连接
268
+ with self.engine.connect() as connection:
269
+
270
+ # 开始一个事务
271
+ with connection.begin(): # 事务会自动提交或回滚
272
+
273
+ try:
274
+
275
+ # 执行 SQL 查询
276
+ result = connection.execute(text(sql_statement))
277
+
278
+ # 执行成功
279
+ logger.success(f"{info} [success]")
280
+
281
+ # 返回查询结果
282
+ if isinstance(save_to_csv, dict) and utils.isTrue(save_to_csv, dict):
283
+ save_to_csv_kwargs: dict = {
284
+ "mode": "w",
285
+ "encoding": "utf-8",
286
+ **save_to_csv
287
+ }
288
+ with open(encoding="utf-8", **save_to_csv_kwargs) as _file:
289
+ return self._result_save(_file, result)
290
+
291
+ return result
292
+
293
+ except Exception as e:
294
+ # 发生异常时回滚事务
295
+ logger.info(f"{info} [failed]")
296
+ logger.exception(e)
297
+ return None
298
+
299
+ # ----------------------------------------------------------------------------------------------
300
+
301
+ def read_with_pandas(
302
+ self,
303
+ method: str = "read_sql",
304
+ result_type: str = "df",
305
+ **kwargs
306
+ ) -> pd.DataFrame | list | dict:
307
+ """读取数据"""
308
+
309
+ # 使用SQL查询数据: 使用 pd.read_sql 的参数
310
+ # read_data_with_pandas(by="sql", result_type="df", sql="SELECT * FROM table ORDER BY date DESC LIMIT 1")
311
+
312
+ # 读取表中的数据: 使用 pd.read_sql_table 的参数
313
+ # read_data_with_pandas(by="table", result_type="df", table_name="ashare")
314
+
315
+ data: pd.DataFrame = pd.DataFrame()
316
+
317
+ if not utils.check_arguments([(method, str, "method")]):
318
+ return data
319
+
320
+ if not utils.check_arguments([(result_type, str, "result_type")]):
321
+ return data
322
+
323
+ info: str = "read data"
324
+
325
+ try:
326
+
327
+ logger.info(f"{info} ......")
328
+
329
+ # 从 kwargs 中删除 con 键
330
+ kwargs.pop('con', None)
331
+
332
+ match method:
333
+ case "read_sql":
334
+ data = pd.read_sql(con=self.engine, **kwargs)
335
+ case "read_sql_query":
336
+ data = pd.read_sql_query(con=self.engine, **kwargs)
337
+ case "read_sql_table":
338
+ data = pd.read_sql_table(con=self.engine, **kwargs)
339
+ case _:
340
+ logger.error(f"{info} [incorrect method: {method}]")
341
+ return data
342
+
343
+ if data.empty:
344
+ logger.error(f"{info} [failed]")
345
+ return data
346
+
347
+ logger.success(f"{info} [success]")
348
+
349
+ match result_type:
350
+ case "json":
351
+ return json.loads(data.to_json(orient='records'))
352
+ case "dict":
353
+ return data.to_dict()
354
+ case "list":
355
+ # https://stackoverflow.com/a/26716774
356
+ return data.to_dict('list')
357
+ case _:
358
+ return data
359
+
360
+ except Exception as e:
361
+ logger.error(f"{info} [failed]")
362
+ logger.exception(e)
363
+ return data
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ezKit
3
- Version: 1.10.5
3
+ Version: 1.10.6
4
4
  Summary: Easy Kit
5
5
  Author: septvean
6
6
  Author-email: septvean@gmail.com
@@ -3,7 +3,7 @@ from setuptools import find_packages, setup
3
3
 
4
4
  setup(
5
5
  name='ezKit',
6
- version='1.10.5',
6
+ version='1.10.6',
7
7
  author='septvean',
8
8
  author_email='septvean@gmail.com',
9
9
  description='Easy Kit',
@@ -1,263 +0,0 @@
1
- """Database"""
2
- # Column, Table, MetaData API
3
- # https://docs.sqlalchemy.org/en/14/core/metadata.html#column-table-metadata-api
4
- # CursorResult
5
- # https://docs.sqlalchemy.org/en/20/core/connections.html#sqlalchemy.engine.CursorResult
6
- # PostgreSQL 14 Data Types
7
- # https://www.postgresql.org/docs/14/datatype.html
8
- import csv
9
- import json
10
- from typing import Any
11
-
12
- import pandas as pd
13
- from loguru import logger
14
- from sqlalchemy import CursorResult, Index, create_engine, text
15
- from sqlalchemy.orm import DeclarativeBase
16
-
17
- from . import utils
18
-
19
-
20
- class Database():
21
- """Database"""
22
-
23
- engine = create_engine('sqlite://')
24
-
25
- def __init__(self, target: str | None = None, **options):
26
- """Initiation"""
27
- if isinstance(target, str) and utils.isTrue(target, str):
28
- if utils.isTrue(options, dict):
29
- self.engine = create_engine(target, **options)
30
- else:
31
- self.engine = create_engine(target)
32
- else:
33
- pass
34
-
35
- # ----------------------------------------------------------------------------------------------
36
-
37
- def initializer(self):
38
- """ensure the parent proc's database connections are not touched in the new connection pool"""
39
- self.engine.dispose(close=False)
40
-
41
- # ----------------------------------------------------------------------------------------------
42
-
43
- def connect_test(self) -> bool:
44
- info = "Database connect test"
45
- try:
46
- logger.info(f"{info} ......")
47
- self.engine.connect()
48
- logger.success(f"{info} [success]")
49
- return True
50
- except Exception as e:
51
- logger.error(f"{info} [failure]")
52
- logger.exception(e)
53
- return False
54
-
55
- # ----------------------------------------------------------------------------------------------
56
-
57
- def metadata_init(self, base: DeclarativeBase, **kwargs) -> bool:
58
- # https://stackoverflow.com/questions/19175311/how-to-create-only-one-table-with-sqlalchemy
59
- info = "Database init table"
60
- try:
61
- logger.info(f"{info} ......")
62
- base.metadata.drop_all(self.engine, **kwargs)
63
- base.metadata.create_all(self.engine, **kwargs)
64
- logger.success(f"{info} [success]")
65
- return True
66
- except Exception as e:
67
- logger.error(f"{info} [failure]")
68
- logger.exception(e)
69
- return False
70
-
71
- # ----------------------------------------------------------------------------------------------
72
-
73
- def create_index(self, index_name, table_field) -> bool:
74
- # 创建索引
75
- # https://stackoverflow.com/a/41254430
76
- # 示例:
77
- # index_name: a_share_list_code_idx1
78
- # table_field: Table_a_share_list.code
79
- info = "Database create index"
80
- try:
81
- logger.info(f"{info} ......")
82
- idx = Index(index_name, table_field)
83
- try:
84
- idx.drop(bind=self.engine)
85
- except Exception as e:
86
- logger.exception(e)
87
- idx.create(bind=self.engine)
88
- logger.success(f'{info} [success]')
89
- return True
90
- except Exception as e:
91
- logger.error(f'{info} [failure]')
92
- logger.error(e)
93
- return False
94
-
95
- # ----------------------------------------------------------------------------------------------
96
-
97
- # 私有函数, 保存 execute 的结果到 CSV 文件
98
- def _result_save(self, file, data) -> bool:
99
- try:
100
- outcsv = csv.writer(file)
101
- outcsv.writerow(data.keys())
102
- outcsv.writerows(data)
103
- return True
104
- except Exception as e:
105
- logger.exception(e)
106
- return False
107
-
108
- # ----------------------------------------------------------------------------------------------
109
-
110
- def execute(
111
- self,
112
- sql: str | None = None,
113
- sql_file: str | None = None,
114
- sql_file_kwargs: dict | None = None,
115
- csv_file: str | None = None,
116
- csv_file_kwargs: dict | None = None
117
- ) -> CursorResult[Any] | bool:
118
- """"运行"""
119
-
120
- # ------------------------------------------------------------
121
-
122
- # 提取 SQL
123
- # 如果 sql 和 sql_file 同时存在, 优先执行 sql
124
-
125
- sql_object = None
126
-
127
- info: str = f"""Extract SQL: {sql}"""
128
-
129
- try:
130
-
131
- logger.info(f"{info} ......")
132
-
133
- if utils.isTrue(sql, str):
134
-
135
- sql_object = sql
136
-
137
- elif sql_file is not None and utils.isTrue(sql_file, str):
138
-
139
- # 判断文件是否存在
140
- if isinstance(sql_file, str) and utils.check_file_type(sql_file, "file") is False:
141
-
142
- logger.error(f"No such file: {sql_file}")
143
- return False
144
-
145
- if isinstance(sql_file, str) and utils.isTrue(sql_file, str):
146
-
147
- # 读取文件内容
148
- if sql_file_kwargs is not None and utils.isTrue(sql_file_kwargs, dict):
149
- with open(sql_file, "r", encoding="utf-8", **sql_file_kwargs) as _file:
150
- sql_object = _file.read()
151
- else:
152
- with open(sql_file, "r", encoding="utf-8") as _file:
153
- sql_object = _file.read()
154
-
155
- else:
156
-
157
- logger.error("SQL or SQL file error")
158
- logger.error(f"{info} [failure]")
159
- return False
160
-
161
- logger.success(f'{info} [success]')
162
-
163
- except Exception as e:
164
-
165
- logger.error(f"{info} [failure]")
166
- logger.exception(e)
167
- return False
168
-
169
- # ------------------------------------------------------------
170
-
171
- # 执行 SQL
172
-
173
- info = f"""Execute SQL: {sql_object}"""
174
-
175
- try:
176
-
177
- logger.info(f"{info} ......")
178
-
179
- with self.engine.connect() as connect:
180
-
181
- # 执行SQL
182
- if sql_object is None:
183
- return False
184
-
185
- result = connect.execute(text(sql_object))
186
-
187
- connect.commit()
188
-
189
- if csv_file is None:
190
- # 如果 csv_file 没有定义, 则直接返回结果
191
- logger.success(f'{info} [success]')
192
- return result
193
-
194
- # 如果 csv_file 有定义, 则保存结果到 csv_file
195
- info_of_save = f"Save result to file: {csv_file}"
196
- logger.info(f"{info_of_save} .......")
197
-
198
- # 保存结果
199
- if isinstance(csv_file_kwargs, dict) and utils.isTrue(csv_file_kwargs, dict):
200
- with open(csv_file, "w", encoding="utf-8", **csv_file_kwargs) as _file:
201
- result_of_save = self._result_save(_file, result)
202
- else:
203
- with open(csv_file, "w", encoding="utf-8") as _file:
204
- result_of_save = self._result_save(_file, result)
205
-
206
- # 检查保存结果
207
- if result_of_save is True:
208
- logger.success(f'{info_of_save} [success]')
209
- logger.success(f'{info} [success]')
210
- return True
211
-
212
- logger.error(f"{info_of_save} [failure]")
213
- logger.error(f"{info} [failure]")
214
- return False
215
-
216
- except Exception as e:
217
-
218
- logger.error(f'{info} [failure]')
219
- logger.exception(e)
220
- return False
221
-
222
- # ----------------------------------------------------------------------------------------------
223
-
224
- def read_data_with_pandas(self, result_type: str = "df", **kwargs) -> pd.DataFrame | dict | list | None:
225
- """读取表中所有数据"""
226
-
227
- # 使用 pd.read_sql_table 的参数
228
- # read_data_with_pandas(result_type="df", table_name="ashare")
229
-
230
- info = f"读取 {kwargs.get('table_name', None)} 表中所有数据"
231
-
232
- try:
233
-
234
- logger.info(f"{info} ......")
235
-
236
- # 从 kwargs 中删除 con 键
237
- kwargs.pop('con', None)
238
-
239
- # 读取数据
240
- data: pd.DataFrame = pd.read_sql_table(con=self.engine, **kwargs)
241
-
242
- if data.empty:
243
- logger.error(f"{info} [失败]")
244
- return None
245
-
246
- logger.success(f"{info} [成功]")
247
-
248
- if utils.isTrue(result_type, str) and result_type == "json":
249
- return json.loads(data.to_json(orient='records'))
250
-
251
- if utils.isTrue(result_type, str) and result_type == "dict":
252
- return data.to_dict()
253
-
254
- if utils.isTrue(result_type, str) and result_type == "list":
255
- # https://stackoverflow.com/a/26716774
256
- return data.to_dict('list')
257
-
258
- return data
259
-
260
- except Exception as e:
261
- logger.error(f"{info} [失败]")
262
- logger.exception(e)
263
- return None
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes