mdbq 4.0.59__tar.gz → 4.0.61__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 (33) hide show
  1. {mdbq-4.0.59 → mdbq-4.0.61}/PKG-INFO +1 -1
  2. mdbq-4.0.61/mdbq/__version__.py +1 -0
  3. {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/mysql/uploader.py +437 -41
  4. {mdbq-4.0.59 → mdbq-4.0.61}/mdbq.egg-info/PKG-INFO +1 -1
  5. mdbq-4.0.59/mdbq/__version__.py +0 -1
  6. {mdbq-4.0.59 → mdbq-4.0.61}/README.txt +0 -0
  7. {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/__init__.py +0 -0
  8. {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/log/__init__.py +0 -0
  9. {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/log/mylogger.py +0 -0
  10. {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/myconf/__init__.py +0 -0
  11. {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/myconf/myconf.py +0 -0
  12. {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/mysql/__init__.py +0 -0
  13. {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/mysql/deduplicator.py +0 -0
  14. {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/mysql/mysql.py +0 -0
  15. {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/mysql/s_query.py +0 -0
  16. {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/mysql/unique_.py +0 -0
  17. {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/other/__init__.py +0 -0
  18. {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/other/download_sku_picture.py +0 -0
  19. {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/other/error_handler.py +0 -0
  20. {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/other/otk.py +0 -0
  21. {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/other/pov_city.py +0 -0
  22. {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/other/ua_sj.py +0 -0
  23. {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/pbix/__init__.py +0 -0
  24. {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/pbix/pbix_refresh.py +0 -0
  25. {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/pbix/refresh_all.py +0 -0
  26. {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/redis/__init__.py +0 -0
  27. {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/redis/getredis.py +0 -0
  28. {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/spider/__init__.py +0 -0
  29. {mdbq-4.0.59 → mdbq-4.0.61}/mdbq.egg-info/SOURCES.txt +0 -0
  30. {mdbq-4.0.59 → mdbq-4.0.61}/mdbq.egg-info/dependency_links.txt +0 -0
  31. {mdbq-4.0.59 → mdbq-4.0.61}/mdbq.egg-info/top_level.txt +0 -0
  32. {mdbq-4.0.59 → mdbq-4.0.61}/setup.cfg +0 -0
  33. {mdbq-4.0.59 → mdbq-4.0.61}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: mdbq
3
- Version: 4.0.59
3
+ Version: 4.0.61
4
4
  Home-page: https://pypi.org/project/mdbq
5
5
  Author: xigua,
6
6
  Author-email: 2587125111@qq.com
@@ -0,0 +1 @@
1
+ VERSION = '4.0.61'
@@ -492,10 +492,30 @@ class MySQLUploader:
492
492
  if date_column and date_column in set_typ:
493
493
  safe_date_col = _index_col_sql(date_column)
494
494
  index_defs.append(f"INDEX `idx_{self._normalize_col(date_column)}` ({safe_date_col})")
495
+
496
+ # 收集所有唯一约束中涉及的列,避免重复创建普通索引
497
+ unique_columns = set()
498
+ if unique_keys:
499
+ for unique_cols in unique_keys:
500
+ if unique_cols:
501
+ for col in unique_cols:
502
+ normalized_col = self._normalize_col(col)
503
+ if normalized_col in set_typ:
504
+ unique_columns.add(normalized_col)
505
+
495
506
  if indexes:
496
507
  for idx_col in indexes:
497
508
  normalized_idx_col = self._normalize_col(idx_col)
498
509
  if normalized_idx_col in set_typ:
510
+ # 检查是否与唯一约束冲突
511
+ if normalized_idx_col in unique_columns:
512
+ logger.warning('索引列已在唯一约束中定义,跳过普通索引', {
513
+ '库': db_name,
514
+ '表': table_name,
515
+ '列': idx_col,
516
+ '原因': '列已在唯一约束中定义'
517
+ })
518
+ continue
499
519
  safe_idx_col = _index_col_sql(idx_col)
500
520
  index_defs.append(f"INDEX `idx_{normalized_idx_col}` ({safe_idx_col})")
501
521
  else:
@@ -1232,22 +1252,48 @@ class MySQLUploader:
1232
1252
  :param table_name: 表名
1233
1253
  :param data: 要上传的数据,支持字典、字典列表或DataFrame格式
1234
1254
  :param set_typ: 列名和数据类型字典 {列名: 数据类型}
1235
- :param primary_keys: 主键列列表,可选
1255
+ :param primary_keys: 主键列列表,可选。格式:['col1', 'col2'] 或 None
1236
1256
  :param check_duplicate: 是否检查重复数据,默认为False
1237
- :param duplicate_columns: 用于检查重复的列,可选
1257
+ :param duplicate_columns: 用于检查重复的列,可选。格式:['col1', 'col2'] 或 None
1238
1258
  :param allow_null: 是否允许空值,默认为False
1239
1259
  :param partition_by: 分表方式('year'、'month'、'None'),可选
1240
1260
  :param partition_date_column: 用于分表的日期列名,默认为'日期', 默认会添加为索引
1241
1261
  :param auto_create: 表不存在时是否自动创建,默认为True
1242
- :param indexes: 需要创建索引的列列表,可选
1262
+ :param indexes: 需要创建索引的列列表,可选。格式:['col1', 'col2'] 或 None
1243
1263
  :param update_on_duplicate: 遇到重复数据时是否更新旧数据,默认为False
1244
1264
  :param transaction_mode: 事务模式,可选值:
1245
1265
  - 'row' : 逐行提交事务(错误隔离性好)
1246
1266
  - 'batch' : 整批提交事务(性能最优)
1247
1267
  - 'hybrid' : 混合模式(每N行提交,平衡性能与安全性)
1248
- :param unique_keys: 唯一约束列表,每个元素为列名列表,支持多列组合唯一约束
1268
+ :param unique_keys: 唯一约束列表,每个元素为列名列表,支持多列组合唯一约束。格式:[['col1', 'col2'], ['col3']] 或 None
1249
1269
  :raises: 可能抛出各种验证和数据库相关异常
1250
1270
 
1271
+ ---
1272
+ 参数格式验证:
1273
+
1274
+ - primary_keys: 必须是字符串列表或None,如 ['col1', 'col2']
1275
+ - indexes: 必须是字符串列表或None,如 ['col1', 'col2']
1276
+ - unique_keys: 必须是嵌套列表或None,如 [['col1', 'col2'], ['col3']]
1277
+ - 错误示例:unique_keys=['col1', 'col2'] (应该是 [['col1', 'col2']])
1278
+ - 所有列名不能为空字符串,会自动去除首尾空格
1279
+ - 重复的列名会被自动去重
1280
+
1281
+ 空值处理规则:
1282
+ - None: 直接返回None,忽略此参数
1283
+ - []: 空列表,返回None,忽略此参数
1284
+ - [[]]: 包含空列表,跳过空列表,如果最终为空则返回None
1285
+ - ['']: 包含空字符串,抛出异常(不允许空字符串)
1286
+ - [' ']: 包含纯空白字符,抛出异常(不允许纯空白字符)
1287
+ - ['', 'col1']: 混合空字符串和有效字符串,跳过空字符串,保留有效字符串
1288
+
1289
+ ---
1290
+ 关于 indexes 和 unique_keys 参数:
1291
+
1292
+ - indexes 创建普通索引,unique_keys 创建唯一约束
1293
+ - 如果同一列同时出现在 indexes 和 unique_keys 中,系统会优先创建唯一约束,跳过普通索引
1294
+ - 唯一约束本身就具有索引功能,因此不会重复创建普通索引
1295
+ - 建议:如果某列需要唯一性约束,直接使用 unique_keys 参数,无需在 indexes 中重复指定
1296
+
1251
1297
  ---
1252
1298
  unique_keys、check_duplicate、update_on_duplicate 三者组合下的行为总结:
1253
1299
 
@@ -1278,29 +1324,34 @@ class MySQLUploader:
1278
1324
  total_skipped = 0
1279
1325
  total_failed = 0
1280
1326
 
1281
- logger.info("开始上传", {
1282
- '库': db_name,
1283
- '表': table_name,
1284
- '批次': batch_id,
1285
- '传入': len(data) if hasattr(data, '__len__') else 1,
1286
- '参数': {
1287
- '主键': primary_keys,
1288
- '去重': check_duplicate,
1289
- '去重列': duplicate_columns,
1290
- '允许空值': allow_null,
1291
- '分表方式': partition_by,
1292
- '分表列': partition_date_column,
1293
- # '自动建表': auto_create,
1294
- '索引': indexes,
1295
- '更新旧数据': update_on_duplicate,
1296
- '事务模式': transaction_mode,
1297
- '唯一约束': unique_keys
1298
- },
1299
- # '数据样例': self._shorten_for_log(data, 2)
1300
- })
1301
-
1302
1327
  try:
1303
- # 验证参数
1328
+ # 验证参数格式
1329
+ validated_primary_keys = self._validate_primary_keys_format(primary_keys, db_name, table_name)
1330
+ validated_indexes = self._validate_indexes_format(indexes, db_name, table_name)
1331
+ validated_unique_keys = self._validate_unique_keys_format(unique_keys, db_name, table_name)
1332
+
1333
+ logger.info("开始上传", {
1334
+ '库': db_name,
1335
+ '表': table_name,
1336
+ '批次': batch_id,
1337
+ '传入': len(data) if hasattr(data, '__len__') else 1,
1338
+ '参数': {
1339
+ '主键': validated_primary_keys,
1340
+ '去重': check_duplicate,
1341
+ '去重列': duplicate_columns,
1342
+ '允许空值': allow_null,
1343
+ '分表方式': partition_by,
1344
+ '分表列': partition_date_column,
1345
+ # '自动建表': auto_create,
1346
+ '索引': validated_indexes,
1347
+ '更新旧数据': update_on_duplicate,
1348
+ '事务模式': transaction_mode,
1349
+ '唯一约束': validated_unique_keys
1350
+ },
1351
+ # '数据样例': self._shorten_for_log(data, 2)
1352
+ })
1353
+
1354
+ # 验证分表参数
1304
1355
  if partition_by:
1305
1356
  partition_by = str(partition_by).lower()
1306
1357
  if partition_by not in ['year', 'month']:
@@ -1370,10 +1421,10 @@ class MySQLUploader:
1370
1421
  try:
1371
1422
  inserted, skipped, failed = self._upload_to_table(
1372
1423
  db_name, part_table, part_data, filtered_set_typ,
1373
- primary_keys, check_duplicate, duplicate_columns,
1424
+ validated_primary_keys, check_duplicate, duplicate_columns,
1374
1425
  allow_null, auto_create, partition_date_column,
1375
- indexes, batch_id, update_on_duplicate, transaction_mode,
1376
- unique_keys
1426
+ validated_indexes, batch_id, update_on_duplicate, transaction_mode,
1427
+ validated_unique_keys
1377
1428
  )
1378
1429
  total_inserted += inserted
1379
1430
  total_skipped += skipped
@@ -1397,10 +1448,10 @@ class MySQLUploader:
1397
1448
  # 不分表,直接上传
1398
1449
  inserted, skipped, failed = self._upload_to_table(
1399
1450
  db_name, table_name, prepared_data, filtered_set_typ,
1400
- primary_keys, check_duplicate, duplicate_columns,
1451
+ validated_primary_keys, check_duplicate, duplicate_columns,
1401
1452
  allow_null, auto_create, partition_date_column,
1402
- indexes, batch_id, update_on_duplicate, transaction_mode,
1403
- unique_keys
1453
+ validated_indexes, batch_id, update_on_duplicate, transaction_mode,
1454
+ validated_unique_keys
1404
1455
  )
1405
1456
  total_inserted = inserted
1406
1457
  total_skipped = skipped
@@ -1435,8 +1486,16 @@ class MySQLUploader:
1435
1486
  '失败': total_failed
1436
1487
  })
1437
1488
 
1438
- # 更新索引
1439
- self._update_indexes(db_name, table_name, indexes)
1489
+ # 更新索引(只有在成功时才执行)
1490
+ if success_flag and 'validated_indexes' in locals():
1491
+ try:
1492
+ self._update_indexes(db_name, table_name, validated_indexes)
1493
+ except Exception as e:
1494
+ logger.warning('更新索引时发生错误', {
1495
+ '库': db_name,
1496
+ '表': table_name,
1497
+ '错误': str(e)
1498
+ })
1440
1499
  return True
1441
1500
 
1442
1501
  @_execute_with_retry
@@ -1835,6 +1894,7 @@ class MySQLUploader:
1835
1894
  def _update_indexes(self, db_name: str, table_name: str, indexes: Optional[List[str]]):
1836
1895
  """
1837
1896
  更新索引,避免重复添加或更新,同时注意大小写一致性。
1897
+ 注意:如果列已经在unique_keys中定义,则不会重复创建普通索引。
1838
1898
 
1839
1899
  :param db_name: 数据库名
1840
1900
  :param table_name: 表名
@@ -1846,7 +1906,7 @@ class MySQLUploader:
1846
1906
  # 规范化索引列名
1847
1907
  normalized_indexes = [self._normalize_col(idx) for idx in indexes]
1848
1908
 
1849
- # 获取现有索引
1909
+ # 获取现有索引(包括普通索引和唯一约束)
1850
1910
  try:
1851
1911
  existing_indexes = self._get_existing_indexes(db_name, table_name)
1852
1912
  except Exception as e:
@@ -1855,13 +1915,20 @@ class MySQLUploader:
1855
1915
 
1856
1916
  # 获取表中现有的列名
1857
1917
  try:
1858
- existing_columns = self._get_existing_indexes(db_name, table_name)
1918
+ existing_columns = self._get_table_columns(db_name, table_name)
1859
1919
  except Exception as e:
1860
1920
  logger.error('获取现有列时发生错误', {'库': db_name, '表': table_name, '错误': str(e)})
1861
1921
  raise
1862
1922
 
1863
- # 找出需要添加的索引
1864
- indexes_to_add = [idx for idx in normalized_indexes if idx not in existing_indexes and idx in existing_columns]
1923
+ # 找出需要添加的索引(排除已存在的索引和不在表中的列)
1924
+ indexes_to_add = []
1925
+ for idx in normalized_indexes:
1926
+ if idx not in existing_indexes and idx in existing_columns:
1927
+ indexes_to_add.append(idx)
1928
+ elif idx in existing_indexes:
1929
+ logger.debug('索引已存在,跳过', {'库': db_name, '表': table_name, '列': idx})
1930
+ elif idx not in existing_columns:
1931
+ logger.warning('索引列不存在于表中,跳过', {'库': db_name, '表': table_name, '列': idx})
1865
1932
 
1866
1933
  # 添加新索引
1867
1934
  for idx in indexes_to_add:
@@ -1873,7 +1940,7 @@ class MySQLUploader:
1873
1940
 
1874
1941
  def _get_existing_indexes(self, db_name: str, table_name: str) -> Set[str]:
1875
1942
  """
1876
- 获取表中现有的索引列名。
1943
+ 获取表中现有的索引列名(包括普通索引和唯一约束)。
1877
1944
 
1878
1945
  :param db_name: 数据库名
1879
1946
  :param table_name: 表名
@@ -2007,6 +2074,222 @@ class MySQLUploader:
2007
2074
  conn.rollback()
2008
2075
  raise
2009
2076
 
2077
+ def _validate_unique_keys_format(self, unique_keys: Optional[List[List[str]]], db_name: str = None, table_name: str = None) -> Optional[List[List[str]]]:
2078
+ """
2079
+ 验证unique_keys参数的格式是否正确
2080
+
2081
+ :param unique_keys: 唯一约束列表
2082
+ :param db_name: 数据库名,用于日志记录
2083
+ :param table_name: 表名,用于日志记录
2084
+ :return: 验证后的unique_keys,如果验证失败则抛出异常
2085
+ :raises ValueError: 当参数格式不正确时抛出
2086
+ """
2087
+ if unique_keys is None:
2088
+ return None
2089
+
2090
+ if not isinstance(unique_keys, list):
2091
+ error_msg = f"unique_keys参数必须是列表类型,当前类型: {type(unique_keys).__name__}"
2092
+ logger.error(error_msg, {'库': db_name, '表': table_name, 'unique_keys': unique_keys})
2093
+ raise ValueError(error_msg)
2094
+
2095
+ # 检查是否为空列表
2096
+ if len(unique_keys) == 0:
2097
+ logger.warning('unique_keys为空列表,将忽略此参数', {'库': db_name, '表': table_name})
2098
+ return None
2099
+
2100
+ validated_keys = []
2101
+ empty_groups_count = 0
2102
+
2103
+ for i, key_group in enumerate(unique_keys):
2104
+ # 检查每个元素是否为列表
2105
+ if not isinstance(key_group, list):
2106
+ error_msg = f"unique_keys[{i}]必须是列表类型,当前类型: {type(key_group).__name__},值: {key_group}"
2107
+ logger.error(error_msg, {'库': db_name, '表': table_name, 'unique_keys': unique_keys})
2108
+ raise ValueError(error_msg)
2109
+
2110
+ # 检查是否为空列表
2111
+ if len(key_group) == 0:
2112
+ empty_groups_count += 1
2113
+ logger.warning(f'unique_keys[{i}]为空列表,跳过', {'库': db_name, '表': table_name})
2114
+ continue
2115
+
2116
+ # 检查每个列名是否为字符串
2117
+ validated_group = []
2118
+ for j, col_name in enumerate(key_group):
2119
+ if not isinstance(col_name, str):
2120
+ error_msg = f"unique_keys[{i}][{j}]必须是字符串类型,当前类型: {type(col_name).__name__},值: {col_name}"
2121
+ logger.error(error_msg, {'库': db_name, '表': table_name, 'unique_keys': unique_keys})
2122
+ raise ValueError(error_msg)
2123
+
2124
+ # 检查是否为空字符串或纯空白字符
2125
+ stripped_name = col_name.strip()
2126
+ if not stripped_name:
2127
+ error_msg = f"unique_keys[{i}][{j}]不能为空字符串或纯空白字符,原始值: '{col_name}'"
2128
+ logger.error(error_msg, {'库': db_name, '表': table_name, 'unique_keys': unique_keys})
2129
+ raise ValueError(error_msg)
2130
+
2131
+ validated_group.append(stripped_name)
2132
+
2133
+ # 去重并检查是否有重复列名
2134
+ if len(validated_group) != len(set(validated_group)):
2135
+ error_msg = f"unique_keys[{i}]中存在重复列名: {validated_group}"
2136
+ logger.error(error_msg, {'库': db_name, '表': table_name, 'unique_keys': unique_keys})
2137
+ raise ValueError(error_msg)
2138
+
2139
+ validated_keys.append(validated_group)
2140
+
2141
+ # 检查验证后的结果
2142
+ if not validated_keys:
2143
+ if empty_groups_count > 0:
2144
+ logger.warning(f'unique_keys包含{empty_groups_count}个空列表,验证后为空,将忽略此参数', {
2145
+ '库': db_name, '表': table_name, '空列表数量': empty_groups_count
2146
+ })
2147
+ else:
2148
+ logger.warning('unique_keys验证后为空,将忽略此参数', {'库': db_name, '表': table_name})
2149
+ return None
2150
+
2151
+ logger.debug('unique_keys格式验证通过', {
2152
+ '库': db_name,
2153
+ '表': table_name,
2154
+ '原始': unique_keys,
2155
+ '验证后': validated_keys,
2156
+ '跳过的空列表': empty_groups_count
2157
+ })
2158
+ return validated_keys
2159
+
2160
+ def _validate_indexes_format(self, indexes: Optional[List[str]], db_name: str = None, table_name: str = None) -> Optional[List[str]]:
2161
+ """
2162
+ 验证indexes参数的格式是否正确
2163
+
2164
+ :param indexes: 索引列列表
2165
+ :param db_name: 数据库名,用于日志记录
2166
+ :param table_name: 表名,用于日志记录
2167
+ :return: 验证后的indexes,如果验证失败则抛出异常
2168
+ :raises ValueError: 当参数格式不正确时抛出
2169
+ """
2170
+ if indexes is None:
2171
+ return None
2172
+
2173
+ if not isinstance(indexes, list):
2174
+ error_msg = f"indexes参数必须是列表类型,当前类型: {type(indexes).__name__}"
2175
+ logger.error(error_msg, {'库': db_name, '表': table_name, 'indexes': indexes})
2176
+ raise ValueError(error_msg)
2177
+
2178
+ # 检查是否为空列表
2179
+ if len(indexes) == 0:
2180
+ logger.warning('indexes为空列表,将忽略此参数', {'库': db_name, '表': table_name})
2181
+ return None
2182
+
2183
+ validated_indexes = []
2184
+ empty_strings_count = 0
2185
+
2186
+ for i, col_name in enumerate(indexes):
2187
+ if not isinstance(col_name, str):
2188
+ error_msg = f"indexes[{i}]必须是字符串类型,当前类型: {type(col_name).__name__},值: {col_name}"
2189
+ logger.error(error_msg, {'库': db_name, '表': table_name, 'indexes': indexes})
2190
+ raise ValueError(error_msg)
2191
+
2192
+ # 检查是否为空字符串或纯空白字符
2193
+ stripped_name = col_name.strip()
2194
+ if not stripped_name:
2195
+ empty_strings_count += 1
2196
+ logger.warning(f'indexes[{i}]为空字符串或纯空白字符,跳过,原始值: "{col_name}"', {
2197
+ '库': db_name, '表': table_name, 'indexes': indexes
2198
+ })
2199
+ continue
2200
+
2201
+ validated_indexes.append(stripped_name)
2202
+
2203
+ # 去重
2204
+ validated_indexes = list(dict.fromkeys(validated_indexes))
2205
+
2206
+ # 检查验证后的结果
2207
+ if not validated_indexes:
2208
+ if empty_strings_count > 0:
2209
+ logger.warning(f'indexes包含{empty_strings_count}个空字符串,验证后为空,将忽略此参数', {
2210
+ '库': db_name, '表': table_name, '空字符串数量': empty_strings_count
2211
+ })
2212
+ else:
2213
+ logger.warning('indexes验证后为空,将忽略此参数', {'库': db_name, '表': table_name})
2214
+ return None
2215
+
2216
+ logger.debug('indexes格式验证通过', {
2217
+ '库': db_name,
2218
+ '表': table_name,
2219
+ '原始': indexes,
2220
+ '验证后': validated_indexes,
2221
+ '跳过的空字符串': empty_strings_count
2222
+ })
2223
+ return validated_indexes
2224
+
2225
+ def _validate_primary_keys_format(self, primary_keys: Optional[List[str]], db_name: str = None, table_name: str = None) -> Optional[List[str]]:
2226
+ """
2227
+ 验证primary_keys参数的格式是否正确
2228
+
2229
+ :param primary_keys: 主键列列表
2230
+ :param db_name: 数据库名,用于日志记录
2231
+ :param table_name: 表名,用于日志记录
2232
+ :return: 验证后的primary_keys,如果验证失败则抛出异常
2233
+ :raises ValueError: 当参数格式不正确时抛出
2234
+ """
2235
+ if primary_keys is None:
2236
+ return None
2237
+
2238
+ if not isinstance(primary_keys, list):
2239
+ error_msg = f"primary_keys参数必须是列表类型,当前类型: {type(primary_keys).__name__}"
2240
+ logger.error(error_msg, {'库': db_name, '表': table_name, 'primary_keys': primary_keys})
2241
+ raise ValueError(error_msg)
2242
+
2243
+ # 检查是否为空列表
2244
+ if len(primary_keys) == 0:
2245
+ logger.warning('primary_keys为空列表,将忽略此参数', {'库': db_name, '表': table_name})
2246
+ return None
2247
+
2248
+ validated_keys = []
2249
+ empty_strings_count = 0
2250
+
2251
+ for i, col_name in enumerate(primary_keys):
2252
+ if not isinstance(col_name, str):
2253
+ error_msg = f"primary_keys[{i}]必须是字符串类型,当前类型: {type(col_name).__name__},值: {col_name}"
2254
+ logger.error(error_msg, {'库': db_name, '表': table_name, 'primary_keys': primary_keys})
2255
+ raise ValueError(error_msg)
2256
+
2257
+ # 检查是否为空字符串或纯空白字符
2258
+ stripped_name = col_name.strip()
2259
+ if not stripped_name:
2260
+ empty_strings_count += 1
2261
+ logger.warning(f'primary_keys[{i}]为空字符串或纯空白字符,跳过,原始值: "{col_name}"', {
2262
+ '库': db_name, '表': table_name, 'primary_keys': primary_keys
2263
+ })
2264
+ continue
2265
+
2266
+ validated_keys.append(stripped_name)
2267
+
2268
+ # 去重并检查是否有重复列名
2269
+ if len(validated_keys) != len(set(validated_keys)):
2270
+ error_msg = f"primary_keys中存在重复列名: {validated_keys}"
2271
+ logger.error(error_msg, {'库': db_name, '表': table_name, 'primary_keys': primary_keys})
2272
+ raise ValueError(error_msg)
2273
+
2274
+ # 检查验证后的结果
2275
+ if not validated_keys:
2276
+ if empty_strings_count > 0:
2277
+ logger.warning(f'primary_keys包含{empty_strings_count}个空字符串,验证后为空,将忽略此参数', {
2278
+ '库': db_name, '表': table_name, '空字符串数量': empty_strings_count
2279
+ })
2280
+ else:
2281
+ logger.warning('primary_keys验证后为空,将忽略此参数', {'库': db_name, '表': table_name})
2282
+ return None
2283
+
2284
+ logger.debug('primary_keys格式验证通过', {
2285
+ '库': db_name,
2286
+ '表': table_name,
2287
+ '原始': primary_keys,
2288
+ '验证后': validated_keys,
2289
+ '跳过的空字符串': empty_strings_count
2290
+ })
2291
+ return validated_keys
2292
+
2010
2293
 
2011
2294
  def main():
2012
2295
  dir_path = os.path.expanduser("~")
@@ -2042,7 +2325,120 @@ def main():
2042
2325
  {'日期': '2023-02-20', 'name': 'Bob', 'AGE': 25, 'salary': 45000.75},
2043
2326
  ]
2044
2327
 
2045
- # 上传数据
2328
+ # 测试参数验证功能
2329
+ print("=== 测试参数验证功能 ===")
2330
+
2331
+ # 正确的格式
2332
+ print("1. 测试正确的unique_keys格式:")
2333
+ try:
2334
+ valid_unique_keys = [['日期', 'name'], ['age']]
2335
+ result = uploader._validate_unique_keys_format(valid_unique_keys, 'test_db', 'test_table')
2336
+ print(f" 通过: {result}")
2337
+ except Exception as e:
2338
+ print(f" 失败: {e}")
2339
+
2340
+ # 错误的格式 - 缺少一层嵌套
2341
+ print("2. 测试错误的unique_keys格式 (缺少嵌套):")
2342
+ try:
2343
+ invalid_unique_keys = ['日期', 'name'] # 错误:应该是 [['日期', 'name']]
2344
+ result = uploader._validate_unique_keys_format(invalid_unique_keys, 'test_db', 'test_table')
2345
+ print(f" 通过: {result}")
2346
+ except Exception as e:
2347
+ print(f" 正确捕获错误: {e}")
2348
+
2349
+ # 错误的格式 - 包含非字符串元素
2350
+ print("3. 测试错误的unique_keys格式 (非字符串元素):")
2351
+ try:
2352
+ invalid_unique_keys = [['日期', 123]] # 错误:123不是字符串
2353
+ result = uploader._validate_unique_keys_format(invalid_unique_keys, 'test_db', 'test_table')
2354
+ print(f" 通过: {result}")
2355
+ except Exception as e:
2356
+ print(f" 正确捕获错误: {e}")
2357
+
2358
+ # 错误的格式 - 空字符串
2359
+ print("4. 测试错误的unique_keys格式 (空字符串):")
2360
+ try:
2361
+ invalid_unique_keys = [['日期', '']] # 错误:空字符串
2362
+ result = uploader._validate_unique_keys_format(invalid_unique_keys, 'test_db', 'test_table')
2363
+ print(f" 通过: {result}")
2364
+ except Exception as e:
2365
+ print(f" 正确捕获错误: {e}")
2366
+
2367
+ # 错误的格式 - 重复列名
2368
+ print("5. 测试错误的unique_keys格式 (重复列名):")
2369
+ try:
2370
+ invalid_unique_keys = [['日期', '日期']] # 错误:重复列名
2371
+ result = uploader._validate_unique_keys_format(invalid_unique_keys, 'test_db', 'test_table')
2372
+ print(f" 通过: {result}")
2373
+ except Exception as e:
2374
+ print(f" 正确捕获错误: {e}")
2375
+
2376
+ # 空值测试 - 空列表
2377
+ print("6. 测试空值情况 - 空列表:")
2378
+ try:
2379
+ empty_list = []
2380
+ result = uploader._validate_unique_keys_format(empty_list, 'test_db', 'test_table')
2381
+ print(f" 通过: {result}")
2382
+ except Exception as e:
2383
+ print(f" 失败: {e}")
2384
+
2385
+ # 空值测试 - 包含空列表
2386
+ print("7. 测试空值情况 - 包含空列表 [[]]:")
2387
+ try:
2388
+ empty_nested = [[]]
2389
+ result = uploader._validate_unique_keys_format(empty_nested, 'test_db', 'test_table')
2390
+ print(f" 通过: {result}")
2391
+ except Exception as e:
2392
+ print(f" 失败: {e}")
2393
+
2394
+ # 空值测试 - 混合空列表和有效列表
2395
+ print("8. 测试空值情况 - 混合空列表和有效列表 [[], ['col1']]:")
2396
+ try:
2397
+ mixed_empty = [[], ['col1']]
2398
+ result = uploader._validate_unique_keys_format(mixed_empty, 'test_db', 'test_table')
2399
+ print(f" 通过: {result}")
2400
+ except Exception as e:
2401
+ print(f" 失败: {e}")
2402
+
2403
+ # 空值测试 - 包含空字符串的列表
2404
+ print("9. 测试空值情况 - 包含空字符串的列表 [[''], ['col1']]:")
2405
+ try:
2406
+ empty_string_list = [[''], ['col1']]
2407
+ result = uploader._validate_unique_keys_format(empty_string_list, 'test_db', 'test_table')
2408
+ print(f" 通过: {result}")
2409
+ except Exception as e:
2410
+ print(f" 正确捕获错误: {e}")
2411
+
2412
+ # 空值测试 - 包含纯空白字符的列表
2413
+ print("10. 测试空值情况 - 包含纯空白字符的列表 [[' '], ['col1']]:")
2414
+ try:
2415
+ whitespace_list = [[' '], ['col1']]
2416
+ result = uploader._validate_unique_keys_format(whitespace_list, 'test_db', 'test_table')
2417
+ print(f" 通过: {result}")
2418
+ except Exception as e:
2419
+ print(f" 正确捕获错误: {e}")
2420
+
2421
+ # 测试indexes的空值处理
2422
+ print("\n=== 测试indexes空值处理 ===")
2423
+ print("11. 测试indexes包含空字符串 ['', 'col1']:")
2424
+ try:
2425
+ indexes_with_empty = ['', 'col1']
2426
+ result = uploader._validate_indexes_format(indexes_with_empty, 'test_db', 'test_table')
2427
+ print(f" 通过: {result}")
2428
+ except Exception as e:
2429
+ print(f" 失败: {e}")
2430
+
2431
+ # 测试primary_keys的空值处理
2432
+ print("12. 测试primary_keys包含空字符串 ['', 'col1']:")
2433
+ try:
2434
+ primary_keys_with_empty = ['', 'col1']
2435
+ result = uploader._validate_primary_keys_format(primary_keys_with_empty, 'test_db', 'test_table')
2436
+ print(f" 通过: {result}")
2437
+ except Exception as e:
2438
+ print(f" 失败: {e}")
2439
+
2440
+ # 上传数据(使用正确的格式)
2441
+ print("\n=== 开始上传数据 ===")
2046
2442
  uploader.upload_data(
2047
2443
  db_name='测试库',
2048
2444
  table_name='测试表',
@@ -2057,7 +2453,7 @@ def main():
2057
2453
  partition_date_column='日期', # 用于分表的日期列名,默认为'日期'
2058
2454
  indexes=[], # 普通索引列
2059
2455
  transaction_mode='row', # 事务模式
2060
- unique_keys=[['日期', 'name', 'age']], # 唯一约束列表
2456
+ unique_keys=[['日期', 'name', 'age']], # 唯一约束列表 - 正确的格式
2061
2457
  )
2062
2458
 
2063
2459
  uploader.close()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: mdbq
3
- Version: 4.0.59
3
+ Version: 4.0.61
4
4
  Home-page: https://pypi.org/project/mdbq
5
5
  Author: xigua,
6
6
  Author-email: 2587125111@qq.com
@@ -1 +0,0 @@
1
- VERSION = '4.0.59'
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
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