cppackage 0.3.1__py3-none-any.whl → 0.3.2__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.
- CPpackage/db/sql_model.py +101 -21
- CPpackage/db/test.py +2 -2
- {cppackage-0.3.1.dist-info → cppackage-0.3.2.dist-info}/METADATA +5 -2
- cppackage-0.3.2.dist-info/RECORD +13 -0
- {cppackage-0.3.1.dist-info → cppackage-0.3.2.dist-info}/WHEEL +1 -1
- {cppackage-0.3.1.dist-info → cppackage-0.3.2.dist-info}/entry_points.txt +1 -0
- cppackage-0.3.1.dist-info/RECORD +0 -13
- {cppackage-0.3.1.dist-info → cppackage-0.3.2.dist-info}/LICENSE +0 -0
- {cppackage-0.3.1.dist-info → cppackage-0.3.2.dist-info}/top_level.txt +0 -0
CPpackage/db/sql_model.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import pymysql
|
|
2
2
|
import time
|
|
3
|
-
from pymysql import Error
|
|
4
3
|
try:
|
|
5
4
|
from .config import get_db_config
|
|
6
5
|
except (ImportError, ValueError):
|
|
@@ -13,12 +12,8 @@ except (ImportError, ValueError):
|
|
|
13
12
|
|
|
14
13
|
# ===================== 数据库连接 =====================
|
|
15
14
|
|
|
16
|
-
def
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
db = database if database else cfg.get('database')
|
|
20
|
-
prt = port if port else cfg.get('port', 3306)
|
|
21
|
-
|
|
15
|
+
def _create_new_connection(cfg, db, prt):
|
|
16
|
+
"""创建新的数据库连接"""
|
|
22
17
|
return pymysql.connect(
|
|
23
18
|
host=cfg.get('host'),
|
|
24
19
|
user=cfg.get('user'),
|
|
@@ -30,6 +25,33 @@ def _get_connection(database=None, port=None):
|
|
|
30
25
|
)
|
|
31
26
|
|
|
32
27
|
|
|
28
|
+
def _get_connection(database=None, port=None):
|
|
29
|
+
"""
|
|
30
|
+
获取数据库连接,包含健康检查机制
|
|
31
|
+
- 使用 ping(reconnect=True) 检测连接有效性
|
|
32
|
+
- 如果连接失效,自动重新创建连接
|
|
33
|
+
"""
|
|
34
|
+
cfg = get_db_config()
|
|
35
|
+
|
|
36
|
+
db = database if database else cfg.get('database')
|
|
37
|
+
prt = port if port else cfg.get('port', 3306)
|
|
38
|
+
|
|
39
|
+
conn = _create_new_connection(cfg, db, prt)
|
|
40
|
+
|
|
41
|
+
# 连接健康检查:发送 ping 确保连接有效
|
|
42
|
+
try:
|
|
43
|
+
conn.ping(reconnect=False)
|
|
44
|
+
except Exception:
|
|
45
|
+
# 如果 ping 失败,重新创建连接
|
|
46
|
+
try:
|
|
47
|
+
conn.close()
|
|
48
|
+
except Exception:
|
|
49
|
+
pass
|
|
50
|
+
conn = _create_new_connection(cfg, db, prt)
|
|
51
|
+
|
|
52
|
+
return conn
|
|
53
|
+
|
|
54
|
+
|
|
33
55
|
# ===================== 查询操作 =====================
|
|
34
56
|
|
|
35
57
|
def sel_data(sql, params=None, port=None, database=None):
|
|
@@ -139,8 +161,7 @@ def find_duplicate_records(table_name, database, unique_index_fields, port=None)
|
|
|
139
161
|
if not isinstance(unique_index_fields, list) or len(unique_index_fields) == 0:
|
|
140
162
|
print("错误:unique_index_fields 必须是非空列表!")
|
|
141
163
|
return None
|
|
142
|
-
|
|
143
|
-
fields_str = ",".join([f"`{field}`" for field in unique_index_fields])
|
|
164
|
+
|
|
144
165
|
group_by_str = ",".join([f"`{field}`" for field in unique_index_fields])
|
|
145
166
|
|
|
146
167
|
# 核心SQL:查询所有重复记录
|
|
@@ -244,19 +265,46 @@ def delete_duplicate_records(table_name, database, unique_index_fields, port=Non
|
|
|
244
265
|
return None
|
|
245
266
|
# ===================== DataFrame 入库 =====================
|
|
246
267
|
|
|
247
|
-
def
|
|
268
|
+
def _is_connection_error(error_msg):
|
|
269
|
+
"""
|
|
270
|
+
判断是否是数据库连接相关错误
|
|
271
|
+
返回 True 表示是连接错误,可以通过重试解决
|
|
272
|
+
"""
|
|
273
|
+
connection_error_codes = [
|
|
274
|
+
'(0, \'\')', # pymysql InterfaceError: 连接已关闭
|
|
275
|
+
'2003', # Can't connect to MySQL server
|
|
276
|
+
'2006', # MySQL server has gone away
|
|
277
|
+
'2013', # Lost connection to MySQL server during query
|
|
278
|
+
'2014', # Commands out of sync
|
|
279
|
+
'1045', # Access denied
|
|
280
|
+
'2055', # Lost connection to MySQL server at 'xxx'
|
|
281
|
+
]
|
|
282
|
+
return any(code in error_msg for code in connection_error_codes)
|
|
248
283
|
|
|
284
|
+
|
|
285
|
+
def update_datas(df, table_name, database, batch_size=20):
|
|
286
|
+
"""
|
|
287
|
+
将 DataFrame 数据批量插入/更新到数据库
|
|
288
|
+
包含完善的重试机制:
|
|
289
|
+
- 连接错误:最多重试3次,指数退避
|
|
290
|
+
- 字段缺失错误:自动同步字段后重试
|
|
291
|
+
- 其他错误:立即抛出
|
|
292
|
+
"""
|
|
249
293
|
if df.empty:
|
|
250
294
|
print("数据为空,无需入库")
|
|
251
295
|
return
|
|
296
|
+
|
|
252
297
|
cols = list(df.columns)
|
|
253
298
|
col_str = ",".join([f"`{c}`" for c in cols])
|
|
254
299
|
value_tpl = "(" + ",".join(["%s"] * len(cols)) + ")"
|
|
255
300
|
update_clause = ",".join(
|
|
256
301
|
[f"`{c}`=VALUES(`{c}`)" for c in cols if c != "id"]
|
|
257
302
|
)
|
|
303
|
+
|
|
258
304
|
total = len(df)
|
|
259
305
|
success_count = 0
|
|
306
|
+
max_retries = 3 # 最大重试次数
|
|
307
|
+
|
|
260
308
|
for start in range(0, total, batch_size):
|
|
261
309
|
batch_df = df.iloc[start:start + batch_size]
|
|
262
310
|
values_str = ",".join([value_tpl] * len(batch_df))
|
|
@@ -267,28 +315,60 @@ def update_datas(df, table_name, database, batch_size=20):
|
|
|
267
315
|
"""
|
|
268
316
|
data = [tuple(row) for row in batch_df.values]
|
|
269
317
|
flat_data = [v for row in data for v in row]
|
|
270
|
-
|
|
271
|
-
for attempt in range(
|
|
318
|
+
|
|
319
|
+
for attempt in range(max_retries):
|
|
320
|
+
conn = None
|
|
272
321
|
try:
|
|
273
322
|
conn = _get_connection(database)
|
|
323
|
+
# 连接健康检查
|
|
324
|
+
conn.ping(reconnect=False)
|
|
325
|
+
|
|
274
326
|
cursor = conn.cursor()
|
|
275
327
|
cursor.execute(sql, flat_data)
|
|
276
328
|
conn.commit()
|
|
277
329
|
success_count += cursor.rowcount
|
|
278
|
-
break
|
|
330
|
+
break # 成功,跳出重试循环
|
|
331
|
+
|
|
279
332
|
except Exception as e:
|
|
333
|
+
error_str = str(e)
|
|
280
334
|
if conn:
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
335
|
+
try:
|
|
336
|
+
conn.rollback()
|
|
337
|
+
except Exception:
|
|
338
|
+
pass
|
|
339
|
+
|
|
340
|
+
print(f"批次入库失败(start={start}, attempt={attempt+1}/{max_retries}): {e}")
|
|
341
|
+
|
|
342
|
+
# 判断是否是连接相关错误
|
|
343
|
+
if _is_connection_error(error_str):
|
|
344
|
+
if attempt < max_retries - 1:
|
|
345
|
+
# 指数退避:2秒、4秒、8秒
|
|
346
|
+
wait_time = 2 * (2 ** attempt)
|
|
347
|
+
print(f"连接错误,等待{wait_time}秒后重试...")
|
|
348
|
+
time.sleep(wait_time)
|
|
349
|
+
continue
|
|
350
|
+
else:
|
|
351
|
+
# 最后一次重试仍然失败,抛出异常
|
|
352
|
+
raise Exception(f"数据库连接错误,已重试{max_retries}次: {e}")
|
|
353
|
+
|
|
354
|
+
elif '1054, "Unknown column' in error_str:
|
|
355
|
+
# 字段缺失错误,同步字段后重试
|
|
284
356
|
check_and_sync_columns(df, table_name, database)
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
357
|
+
# 字段同步成功后,继续当前批次的重试
|
|
358
|
+
if attempt < max_retries - 1:
|
|
359
|
+
continue
|
|
360
|
+
else:
|
|
361
|
+
raise Exception(f"字段同步后仍然失败: {e}")
|
|
288
362
|
else:
|
|
289
|
-
|
|
363
|
+
# 其他非连接错误,直接抛出
|
|
364
|
+
raise
|
|
365
|
+
|
|
290
366
|
finally:
|
|
291
367
|
if conn:
|
|
292
|
-
|
|
368
|
+
try:
|
|
369
|
+
conn.close()
|
|
370
|
+
except Exception:
|
|
371
|
+
pass
|
|
372
|
+
|
|
293
373
|
print(f"成功插入/更新 {success_count} 条记录")
|
|
294
374
|
|
CPpackage/db/test.py
CHANGED
|
@@ -4,10 +4,10 @@ import os
|
|
|
4
4
|
|
|
5
5
|
if __name__ == "__main__":
|
|
6
6
|
# 配置你的参数
|
|
7
|
-
TABLE_NAME = "
|
|
7
|
+
TABLE_NAME = "pinlei_sp360_xsfx"
|
|
8
8
|
DATABASE_NAME = "shengyicanmou"
|
|
9
9
|
# 你的唯一索引字段列表(必须是列表类型!)
|
|
10
|
-
UNIQUE_FIELDS = ["
|
|
10
|
+
UNIQUE_FIELDS = ["begindate", "value_l", "itemId", "store_id", "date_effect"]
|
|
11
11
|
PORT = 3306 # 可选,默认从配置获取
|
|
12
12
|
|
|
13
13
|
# 第一步:查询重复记录
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: cppackage
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: 超品集团自用的Python包
|
|
5
5
|
Home-page: https://github.com/example/CPpackage
|
|
6
6
|
Author: team-数智组
|
|
7
7
|
Author-email: m110135@163.com
|
|
8
|
+
License: UNKNOWN
|
|
9
|
+
Platform: UNKNOWN
|
|
8
10
|
Classifier: Development Status :: 3 - Alpha
|
|
9
11
|
Classifier: Intended Audience :: Developers
|
|
10
12
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -16,7 +18,6 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
16
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
17
19
|
Requires-Python: >=3.8
|
|
18
20
|
Description-Content-Type: text/markdown
|
|
19
|
-
License-File: LICENSE
|
|
20
21
|
Requires-Dist: pymysql
|
|
21
22
|
Requires-Dist: pandas
|
|
22
23
|
Requires-Dist: numpy
|
|
@@ -70,3 +71,5 @@ CPpackage/
|
|
|
70
71
|
## 许可证
|
|
71
72
|
|
|
72
73
|
本项目采用MIT许可证。详情请参阅LICENSE文件。
|
|
74
|
+
|
|
75
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
CPpackage/__init__.py,sha256=KpVaXkIzkNDl-dfSX8Rr7z9XFlipxx1nZ_GzS4Qls4I,173
|
|
2
|
+
CPpackage/core/__init__.py,sha256=92mF0310uQ5ujlgd-LCMs5kZVzfGRdcl67GHIRWhHcA,504
|
|
3
|
+
CPpackage/db/__init__.py,sha256=WhrLqsjq97KKHF7YV-HcZf9pRtHuqKhRjXDNPCBVbjY,107
|
|
4
|
+
CPpackage/db/config.py,sha256=A9HFCXqlrvssHLD1Ys9HpjDckavalFj31X1Np_aDNp4,729
|
|
5
|
+
CPpackage/db/sql_model.py,sha256=suwQMXf_hpfYfZR07SadIa2E_y_mbEKKy-6BuZCwWE4,13002
|
|
6
|
+
CPpackage/db/test.py,sha256=1EfTt53H9HLIGK6U2SAF6lIDgz2LItJ9tNHpxojz9ro,939
|
|
7
|
+
CPpackage/utils/__init__.py,sha256=sAespT0aoLr3O0WF27DwHLYF60VZgPA1tiT0JJ8s8zI,264
|
|
8
|
+
cppackage-0.3.2.dist-info/LICENSE,sha256=6Pj597LnvlRtq3Vgjkfp6zgBeBh7wl8jKE-nCFg4vZI,1065
|
|
9
|
+
cppackage-0.3.2.dist-info/METADATA,sha256=KFc2Lr9sQj2vjZaiVRAHKd65oMROmyAUBxSbiQDv5Ug,2305
|
|
10
|
+
cppackage-0.3.2.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
|
|
11
|
+
cppackage-0.3.2.dist-info/entry_points.txt,sha256=6MmsHuaQY5w0GxhHAx3lQfHTaocmueEf1OGsoM-hMrQ,51
|
|
12
|
+
cppackage-0.3.2.dist-info/top_level.txt,sha256=FQrfWDbJistWQIrHW_Aoxoy_UWSzjOByWVcVS5ig9Tk,10
|
|
13
|
+
cppackage-0.3.2.dist-info/RECORD,,
|
cppackage-0.3.1.dist-info/RECORD
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
CPpackage/__init__.py,sha256=KpVaXkIzkNDl-dfSX8Rr7z9XFlipxx1nZ_GzS4Qls4I,173
|
|
2
|
-
CPpackage/core/__init__.py,sha256=92mF0310uQ5ujlgd-LCMs5kZVzfGRdcl67GHIRWhHcA,504
|
|
3
|
-
CPpackage/db/__init__.py,sha256=WhrLqsjq97KKHF7YV-HcZf9pRtHuqKhRjXDNPCBVbjY,107
|
|
4
|
-
CPpackage/db/config.py,sha256=A9HFCXqlrvssHLD1Ys9HpjDckavalFj31X1Np_aDNp4,729
|
|
5
|
-
CPpackage/db/sql_model.py,sha256=QnWEBb-ZqioZf62q-RQPgG8uJqLY9KgcyGW3zFkH4YY,10198
|
|
6
|
-
CPpackage/db/test.py,sha256=dEeoV5FBuWEt5MFujrt3fd2KSQ690zyw6mANgeiVRCg,1015
|
|
7
|
-
CPpackage/utils/__init__.py,sha256=sAespT0aoLr3O0WF27DwHLYF60VZgPA1tiT0JJ8s8zI,264
|
|
8
|
-
cppackage-0.3.1.dist-info/LICENSE,sha256=6Pj597LnvlRtq3Vgjkfp6zgBeBh7wl8jKE-nCFg4vZI,1065
|
|
9
|
-
cppackage-0.3.1.dist-info/METADATA,sha256=UZsPKX3T4zA7FbzYpb6Ppy0UvjzFjgq5zXMmZmFjqnE,2287
|
|
10
|
-
cppackage-0.3.1.dist-info/WHEEL,sha256=hPN0AlP2dZM_3ZJZWP4WooepkmU9wzjGgCLCeFjkHLA,92
|
|
11
|
-
cppackage-0.3.1.dist-info/entry_points.txt,sha256=SAzF2klkNyeNCqXuiNkDirN999l7v8CGCrHR0StD_oo,50
|
|
12
|
-
cppackage-0.3.1.dist-info/top_level.txt,sha256=FQrfWDbJistWQIrHW_Aoxoy_UWSzjOByWVcVS5ig9Tk,10
|
|
13
|
-
cppackage-0.3.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|