mdbq 3.9.9__py3-none-any.whl → 3.9.11__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.
mdbq/mysql/uploader.py CHANGED
@@ -99,7 +99,6 @@ class MySQLUploader:
99
99
  :param port: 数据库端口,默认为3306
100
100
  :param charset: 字符集,默认为utf8mb4
101
101
  :param collation: 排序规则,默认为utf8mb4_0900_ai_ci
102
-
103
102
  :param max_retries: 最大重试次数,默认为10
104
103
  :param retry_interval: 重试间隔(秒),默认为10
105
104
  :param pool_size: 连接池大小,默认为5
@@ -122,7 +121,7 @@ class MySQLUploader:
122
121
  self.write_timeout = write_timeout
123
122
  self.ssl = ssl
124
123
  self._prepared_statements = StatementCache(maxsize=100)
125
- self._max_cached_statements = 100
124
+ self._max_cached_statements = 100 # 用于控制 StatementCache 类中缓存的 SQL 语句数量,最多缓存 100 条 SQL 语句
126
125
  self._table_metadata_cache = {}
127
126
  self.metadata_cache_ttl = 300 # 5分钟缓存时间
128
127
 
@@ -352,21 +351,29 @@ class MySQLUploader:
352
351
  :raises ValueError: 如果日期格式无效或分表方式无效
353
352
  """
354
353
  try:
355
- # date_obj = datetime.datetime.strptime(date_value, '%Y-%m-%d %H:%M:%S')
354
+ # date_obj = datetime.datetime.strptime(date_value, '%Y-%m-%d')
356
355
  date_obj = self._validate_datetime(date_value, True)
357
356
  except ValueError:
358
- try:
359
- # date_obj = datetime.datetime.strptime(date_value, '%Y-%m-%d')
360
- date_obj = self._validate_datetime(date_value, True)
361
- except ValueError:
362
- error_msg = f"无效的日期格式1: {date_value}"
363
- logger.error(error_msg)
364
- raise ValueError(error_msg)
357
+ error_msg = f"`{table_name}` 无效的日期格式1: {date_value}"
358
+ logger.error(error_msg)
359
+ raise ValueError(error_msg)
360
+
361
+ # try:
362
+ # # date_obj = datetime.datetime.strptime(date_value, '%Y-%m-%d %H:%M:%S')
363
+ # date_obj = self._validate_datetime(date_value, True)
364
+ # except ValueError:
365
+ # try:
366
+ # # date_obj = datetime.datetime.strptime(date_value, '%Y-%m-%d')
367
+ # date_obj = self._validate_datetime(date_value, True)
368
+ # except ValueError:
369
+ # error_msg = f"无效的日期格式1: {date_value}"
370
+ # logger.error(error_msg)
371
+ # raise ValueError(error_msg)
365
372
 
366
373
  if partition_by == 'year':
367
374
  return f"{table_name}_{date_obj.year}"
368
375
  elif partition_by == 'month':
369
- return f"{table_name}_{date_obj.year}_{date_obj.month:02d}"
376
+ return f"{table_name}_{date_obj.year}-{date_obj.month:02d}" # 这里和兼容旧版 `2025-05`,但可能是个隐患
370
377
  else:
371
378
  error_msg = "分表方式必须是 'year' 或 'month'"
372
379
  logger.error(error_msg)
@@ -386,7 +393,11 @@ class MySQLUploader:
386
393
  raise ValueError(error_msg)
387
394
 
388
395
  # 移除非法字符,只保留字母、数字、下划线和美元符号
389
- cleaned = re.sub(r'[^\w\u4e00-\u9fff$]', '', identifier)
396
+ cleaned = re.sub(r'[^\w\u4e00-\u9fff$]', '_', identifier)
397
+
398
+ # 将多个连续的下划线替换为单个下划线, 移除开头和结尾的下划线
399
+ cleaned = re.sub(r'_+', '_', cleaned).strip('_')
400
+
390
401
  if not cleaned:
391
402
  error_msg = f"无法清理异常标识符: {identifier}"
392
403
  logger.error(error_msg)
@@ -597,7 +608,7 @@ class MySQLUploader:
597
608
  column_type_lower = column_type.lower()
598
609
 
599
610
  # 处理百分比值
600
- if isinstance(value, str) and '%' in value:
611
+ if str(value).strip().endswith('%'):
601
612
  try:
602
613
  # 移除百分号并转换为小数
603
614
  percent_value = float(value.strip().replace('%', ''))
@@ -606,16 +617,22 @@ class MySQLUploader:
606
617
  except ValueError:
607
618
  pass # 如果不是有效的百分比数字,继续正常处理
608
619
 
609
- if 'int' in column_type_lower:
610
- if isinstance(value, (str, bytes)) and not value.strip().isdigit():
611
- raise ValueError("非数字字符串无法转换为整数")
612
- return int(value)
620
+ elif 'int' in column_type_lower:
621
+ if isinstance(value, str):
622
+ # 移除可能的逗号和空格
623
+ value = value.replace(',', '').strip()
624
+ # 尝试转换为浮点数再转整数
625
+ try:
626
+ return int(float(value))
627
+ except ValueError:
628
+ raise ValueError(f"`{value}` 无法转为整数")
629
+ return int(value) if value is not None else None
613
630
  elif any(t in column_type_lower for t in ['float', 'double', 'decimal']):
614
631
  if isinstance(value, str):
615
632
  # 处理可能包含逗号的数字字符串
616
633
  value = value.replace(',', '')
617
634
  return float(value) if value is not None else None
618
- elif '日期' in column_type_lower or 'time' in column_type_lower:
635
+ elif 'date' in column_type_lower or 'time' in column_type_lower:
619
636
  if isinstance(value, (datetime.datetime, pd.Timestamp)):
620
637
  return value.strftime('%Y-%m-%d %H:%M:%S')
621
638
  elif isinstance(value, str):
@@ -630,12 +647,11 @@ class MySQLUploader:
630
647
  return value.replace('\\', '\\\\').replace("'", "\\'")
631
648
  return str(value)
632
649
  elif 'json' in column_type_lower:
633
- import json
634
650
  return json.dumps(value) if value is not None else None
635
651
  else:
636
652
  return value
637
653
  except (ValueError, TypeError) as e:
638
- error_msg = f"数据类型转换异常 {value} to type {column_type}: {str(e)}"
654
+ error_msg = f"转换异常 -> 无法将 `{value}` 的数据类型转为: `{column_type}` -> {str(e)}"
639
655
  logger.error(error_msg)
640
656
  raise ValueError(error_msg)
641
657
 
@@ -681,7 +697,8 @@ class MySQLUploader:
681
697
  auto_create: bool,
682
698
  date_column: Optional[str],
683
699
  indexes: Optional[List[str]],
684
- batch_id: Optional[str] = None
700
+ batch_id: Optional[str] = None,
701
+ update_on_duplicate: bool = False
685
702
  ):
686
703
  """实际执行表上传的方法"""
687
704
  # 检查表是否存在
@@ -711,7 +728,9 @@ class MySQLUploader:
711
728
  # 插入数据
712
729
  self._insert_data(
713
730
  db_name, table_name, data, set_typ,
714
- check_duplicate, duplicate_columns
731
+ check_duplicate, duplicate_columns,
732
+ batch_id=batch_id,
733
+ update_on_duplicate=update_on_duplicate
715
734
  )
716
735
 
717
736
  def _infer_data_type(self, value: Any) -> str:
@@ -721,12 +740,13 @@ class MySQLUploader:
721
740
  :param value: 要推断的值
722
741
  :return: MySQL数据类型字符串
723
742
  """
724
- if value is None:
743
+ if value is None or str(value).lower() in ['', 'none', 'nan']:
725
744
  return 'VARCHAR(255)' # 默认字符串类型
726
745
 
727
746
  # 检查是否是百分比字符串
728
- if isinstance(value, str) and '%' in value:
729
- return 'DECIMAL(10,4)' # 百分比统一使用DECIMAL(10,4)
747
+ if isinstance(value, str):
748
+ if value.endswith('%'):
749
+ return 'DECIMAL(10,4)' # 百分比统一使用DECIMAL(10,4)
730
750
 
731
751
  if isinstance(value, bool):
732
752
  return 'TINYINT(1)'
@@ -773,6 +793,26 @@ class MySQLUploader:
773
793
  else:
774
794
  return 'VARCHAR(255)'
775
795
 
796
+ def normalize_column_names(self, data: Union[pd.DataFrame, List[Dict[str, Any]]]) -> Union[
797
+ pd.DataFrame, List[Dict[str, Any]]]:
798
+ """
799
+ 1. pandas:规范化列名
800
+ 2. 字典列表:规范化每个字典的键
801
+
802
+ 参数:
803
+ data: 输入数据,支持两种类型:
804
+ - pandas.DataFrame:将规范化其列名
805
+ - List[Dict[str, Any]]:将规范化列表中每个字典的键
806
+ """
807
+ if isinstance(data, pd.DataFrame):
808
+ # 处理DataFrame
809
+ data.columns = [self._validate_identifier(col) for col in data.columns]
810
+ return data
811
+ elif isinstance(data, list):
812
+ # 处理字典列表
813
+ return [{self._validate_identifier(k): v for k, v in item.items()} for item in data]
814
+ return data
815
+
776
816
  def _prepare_data(
777
817
  self,
778
818
  data: Union[Dict, List[Dict], pd.DataFrame],
@@ -807,6 +847,9 @@ class MySQLUploader:
807
847
  logger.error(error_msg)
808
848
  raise ValueError(error_msg)
809
849
 
850
+ # 统一处理原始数据中列名的特殊字符
851
+ data = self.normalize_column_names(data)
852
+
810
853
  # 将set_typ的键转为小写
811
854
  set_typ = {k.lower(): v for k, v in set_typ.items()}
812
855
 
@@ -826,11 +869,11 @@ class MySQLUploader:
826
869
  if sample_values:
827
870
  inferred_type = self._infer_data_type(sample_values[0])
828
871
  filtered_set_typ[col] = inferred_type
829
- logger.debug(f"自动推断列'{col}'的数据类型为: {inferred_type}")
872
+ logger.debug(f"自动推断列 `{col}` 的数据类型为: {inferred_type}")
830
873
  else:
831
874
  # 没有样本值,使用默认类型
832
875
  filtered_set_typ[col] = 'VARCHAR(255)'
833
- logger.debug(f"为列'{col}'使用默认数据类型: VARCHAR(255)")
876
+ logger.debug(f"列 `{col}` 使用默认数据类型: VARCHAR(255)")
834
877
 
835
878
  prepared_data = []
836
879
  for row_idx, row in enumerate(data, 1):
@@ -842,7 +885,7 @@ class MySQLUploader:
842
885
 
843
886
  if col_name not in row:
844
887
  if not allow_null:
845
- error_msg = f"Row {row_idx}: Missing required column '{col_name}' in data"
888
+ error_msg = f"行号:{row_idx} -> 缺失列: `{col_name}`"
846
889
  logger.error(error_msg)
847
890
  raise ValueError(error_msg)
848
891
  prepared_row[col_name] = None
@@ -850,7 +893,7 @@ class MySQLUploader:
850
893
  try:
851
894
  prepared_row[col_name] = self._validate_value(row[col_name], filtered_set_typ[col_name], allow_null)
852
895
  except ValueError as e:
853
- error_msg = f"Row {row_idx}, column '{col_name}': {str(e)}"
896
+ error_msg = f"行号:{row_idx}, 列名:`{col_name}`-> 报错: {str(e)}"
854
897
  logger.error(error_msg)
855
898
  raise ValueError(error_msg)
856
899
  prepared_data.append(prepared_row)
@@ -871,7 +914,8 @@ class MySQLUploader:
871
914
  partition_by: Optional[str] = None,
872
915
  partition_date_column: str = '日期',
873
916
  auto_create: bool = True,
874
- indexes: Optional[List[str]] = None
917
+ indexes: Optional[List[str]] = None,
918
+ update_on_duplicate: bool = False
875
919
  ):
876
920
  """
877
921
  上传数据到数据库的主入口方法
@@ -888,6 +932,7 @@ class MySQLUploader:
888
932
  :param partition_date_column: 用于分表的日期列名,默认为'日期'
889
933
  :param auto_create: 表不存在时是否自动创建,默认为True
890
934
  :param indexes: 需要创建索引的列列表,可选
935
+ :param update_on_duplicate: 遇到重复数据时是否更新旧数据(默认为False)
891
936
  :raises: 可能抛出各种验证和数据库相关异常
892
937
  """
893
938
  upload_start = time.time()
@@ -898,25 +943,25 @@ class MySQLUploader:
898
943
 
899
944
  logger.info("开始上传数据", {
900
945
  '批次号': batch_id,
901
- 'database': db_name,
902
- 'table': table_name,
946
+ '': db_name,
947
+ '': table_name,
903
948
  '分表方式': partition_by,
904
- '是否排重': check_duplicate,
949
+ '排重': check_duplicate,
905
950
  '总计行数': len(data) if hasattr(data, '__len__') else 1,
906
951
  '自动建表': auto_create
907
952
  })
908
953
 
909
954
  try:
910
- # # 验证参数
911
- # if not set_typ:
912
- # error_msg = "列的数据类型缺失"
913
- # logger.error(error_msg)
914
- # raise ValueError(error_msg)
915
-
916
- if partition_by and partition_by not in ['year', 'month']:
917
- error_msg = "分表方式必须是 'year' 或 'month'"
918
- logger.error(error_msg)
919
- raise ValueError(error_msg)
955
+ # 验证参数
956
+ if not set_typ:
957
+ logger.debug(f'set_typ 参数缺失,建表不指定数据类型字典,后续存储数据容易引发异常')
958
+
959
+ if partition_by:
960
+ partition_by = str(partition_by).lower()
961
+ if partition_by not in ['year', 'month']:
962
+ error_msg = "分表方式必须是 'year' 或 'month'"
963
+ logger.error(error_msg)
964
+ raise ValueError(error_msg)
920
965
 
921
966
  # 准备数据
922
967
  prepared_data, filtered_set_typ = self._prepare_data(data, set_typ, allow_null)
@@ -962,7 +1007,7 @@ class MySQLUploader:
962
1007
  db_name, part_table, part_data, filtered_set_typ,
963
1008
  primary_keys, check_duplicate, duplicate_columns,
964
1009
  allow_null, auto_create, partition_date_column,
965
- indexes, batch_id
1010
+ indexes, batch_id, update_on_duplicate
966
1011
  )
967
1012
  except Exception as e:
968
1013
  logger.error("分表上传失败", {
@@ -976,7 +1021,7 @@ class MySQLUploader:
976
1021
  db_name, table_name, prepared_data, filtered_set_typ,
977
1022
  primary_keys, check_duplicate, duplicate_columns,
978
1023
  allow_null, auto_create, partition_date_column,
979
- indexes, batch_id
1024
+ indexes, batch_id, update_on_duplicate
980
1025
  )
981
1026
 
982
1027
  success_flag = True
@@ -1004,7 +1049,8 @@ class MySQLUploader:
1004
1049
  check_duplicate: bool = False,
1005
1050
  duplicate_columns: Optional[List[str]] = None,
1006
1051
  batch_size: int = 1000,
1007
- batch_id: Optional[str] = None
1052
+ batch_id: Optional[str] = None,
1053
+ update_on_duplicate: bool = False
1008
1054
  ):
1009
1055
  """
1010
1056
  实际执行数据插入的方法
@@ -1016,6 +1062,7 @@ class MySQLUploader:
1016
1062
  :param check_duplicate: 是否检查重复数据,默认为False
1017
1063
  :param duplicate_columns: 用于检查重复的列,可选
1018
1064
  :param batch_size: 批量插入大小,默认为1000
1065
+ :param update_on_duplicate: 遇到重复数据时是否更新旧数据(默认为False)
1019
1066
  :param batch_id: 批次ID用于日志追踪,可选
1020
1067
  """
1021
1068
  if not data:
@@ -1048,8 +1095,21 @@ class MySQLUploader:
1048
1095
 
1049
1096
  where_clause = " AND ".join(conditions)
1050
1097
 
1051
- sql = f"""
1098
+ if update_on_duplicate:
1099
+ # 更新模式 - 使用ON DUPLICATE KEY UPDATE语法
1100
+ update_clause = ", ".join([f"`{col}` = VALUES(`{col}`)" for col in all_columns])
1101
+ sql = f"""
1052
1102
  INSERT INTO `{db_name}`.`{table_name}`
1103
+ (`{'`,`'.join(safe_columns)}`)
1104
+ VALUES ({placeholders})
1105
+ ON DUPLICATE KEY UPDATE {update_clause}
1106
+ """
1107
+
1108
+ # 注意:在update_on_duplicate模式下,row_values只需要插入数据,不需要排重列值
1109
+ def prepare_values(row):
1110
+ return [row.get(col) for col in all_columns]
1111
+ else:
1112
+ sql = f"""INSERT INTO `{db_name}`.`{table_name}`
1053
1113
  (`{'`,`'.join(safe_columns)}`)
1054
1114
  SELECT {placeholders}
1055
1115
  FROM DUAL
@@ -1058,6 +1118,10 @@ class MySQLUploader:
1058
1118
  WHERE {where_clause}
1059
1119
  )
1060
1120
  """
1121
+
1122
+ # 在check_duplicate模式下,row_values需要插入数据+排重列值
1123
+ def prepare_values(row):
1124
+ return [row.get(col) for col in all_columns] + [row.get(col) for col in duplicate_columns]
1061
1125
  else:
1062
1126
  sql = f"""
1063
1127
  INSERT INTO `{db_name}`.`{table_name}`
@@ -1065,6 +1129,10 @@ class MySQLUploader:
1065
1129
  VALUES ({placeholders})
1066
1130
  """
1067
1131
 
1132
+ # 普通模式下,row_values只需要插入数据
1133
+ def prepare_values(row):
1134
+ return [row.get(col) for col in all_columns]
1135
+
1068
1136
  total_inserted = 0
1069
1137
  total_skipped = 0
1070
1138
  total_failed = 0 # 失败计数器
@@ -1080,11 +1148,7 @@ class MySQLUploader:
1080
1148
  for row in batch:
1081
1149
  try:
1082
1150
  # 准备参数
1083
- row_values = [row.get(col) for col in all_columns]
1084
- # 如果是排重检查,添加排重列值
1085
- if check_duplicate:
1086
- row_values += [row.get(col) for col in duplicate_columns]
1087
-
1151
+ row_values = prepare_values(row)
1088
1152
  cursor.execute(sql, row_values)
1089
1153
  successful_rows += 1
1090
1154
  conn.commit() # 每次成功插入后提交
@@ -1096,13 +1160,13 @@ class MySQLUploader:
1096
1160
  # 记录失败行详细信息
1097
1161
  error_details = {
1098
1162
  '批次号': batch_id,
1099
- 'database': db_name,
1100
- 'table': table_name,
1163
+ '': db_name,
1164
+ '': table_name,
1101
1165
  'error_type': type(e).__name__,
1102
1166
  'error_message': str(e),
1103
- 'column_types': set_typ,
1167
+ '数据类型': set_typ,
1104
1168
  '是否排重': check_duplicate,
1105
- 'duplicate_columns': duplicate_columns
1169
+ '排重列': duplicate_columns
1106
1170
  }
1107
1171
  logger.error(f"单行插入失败: {error_details}")
1108
1172
  continue # 跳过当前行,继续处理下一行
@@ -1227,7 +1291,7 @@ def main():
1227
1291
  # 准备数据
1228
1292
  data = [
1229
1293
  {'日期': '2023-01-8', 'name': 'JACk', 'AGE': '24', 'salary': 555.1545},
1230
- {'日期': '2023-01-15', 'name': 'Alice', 'AGE': 35, 'salary': 100},
1294
+ {'日期': '2023-01-15', 'name': 'Alice', 'AGE': 35, 'salary': '100'},
1231
1295
  {'日期': '2023-01-15', 'name': 'Alice', 'AGE': 30, 'salary': 0.0},
1232
1296
  {'日期': '2023-02-20', 'name': 'Bob', 'AGE': 25, 'salary': 45000.75}
1233
1297
  ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: mdbq
3
- Version: 3.9.9
3
+ Version: 3.9.11
4
4
  Home-page: https://pypi.org/project/mdbq
5
5
  Author: xigua,
6
6
  Author-email: 2587125111@qq.com
@@ -1,5 +1,5 @@
1
1
  mdbq/__init__.py,sha256=Il5Q9ATdX8yXqVxtP_nYqUhExzxPC_qk_WXQ_4h0exg,16
2
- mdbq/__version__.py,sha256=Z38j4uvZuqpFYiUEq0FTd82-1Y90RoVwpNEDWVHNTkk,17
2
+ mdbq/__version__.py,sha256=zVFC_9Hi22FnOaPTZtjaH6okUJz6Vi_VZ5smgBB5eZQ,18
3
3
  mdbq/aggregation/__init__.py,sha256=EeDqX2Aml6SPx8363J-v1lz0EcZtgwIBYyCJV6CcEDU,40
4
4
  mdbq/aggregation/optimize.py,sha256=2oalzD9weZhDclUC22OLxYa8Zj7KnmsGUoUau_Jlyc4,19796
5
5
  mdbq/aggregation/query_data.py,sha256=5_OzjGR5Sq00q-EgAYmSE5V9i4Solw9y4hkldl4mvt8,179808
@@ -10,9 +10,9 @@ mdbq/log/mylogger.py,sha256=jHCVO7KPQrg2kcCaIrakHivZmFBJyy-24sIn2rsbK4Y,24440
10
10
  mdbq/log/spider_logging.py,sha256=-ozWWEGm3HVv604ozs_OOvVwumjokmUPwbaodesUrPY,1664
11
11
  mdbq/mysql/__init__.py,sha256=A_DPJyAoEvTSFojiI2e94zP0FKtCkkwKP1kYUCSyQzo,11
12
12
  mdbq/mysql/deduplicator.py,sha256=brhX3eyE8-kn3nAYweKfBbAkXiNcyw_pL4CTyPqmPBg,21983
13
- mdbq/mysql/mysql.py,sha256=jTcizvUtRdwMhWK2i_LA9yDPmcifLjUzVhwTbC3wfJk,119785
13
+ mdbq/mysql/mysql.py,sha256=0Yaaw178wk_iRxI2PpVGeec_tDal4Fq5jyCyB0scgvQ,55339
14
14
  mdbq/mysql/s_query.py,sha256=X055aLRAgxVvueXx4NbfNjp6MyBI02_XBb1pTKw09L0,8660
15
- mdbq/mysql/uploader.py,sha256=mIgUnV7MwIkrbG-dchMkMzWo_N-XrQROLWTGGGuD_ts,49171
15
+ mdbq/mysql/uploader.py,sha256=_Z0m7nEXzmxY6josxBeQR7nG-bsL8FQHR_RDLjj7IzE,52480
16
16
  mdbq/other/__init__.py,sha256=jso1oHcy6cJEfa7udS_9uO5X6kZLoPBF8l3wCYmr5dM,18
17
17
  mdbq/other/download_sku_picture.py,sha256=YU8DxKMXbdeE1OOKEA848WVp62jYHw5O4tXTjUdq9H0,44832
18
18
  mdbq/other/otk.py,sha256=iclBIFbQbhlqzUbcMMoePXBpcP1eZ06ZtjnhcA_EbmE,7241
@@ -25,7 +25,7 @@ mdbq/redis/__init__.py,sha256=YtgBlVSMDphtpwYX248wGge1x-Ex_mMufz4-8W0XRmA,12
25
25
  mdbq/redis/getredis.py,sha256=Uk8-cOWT0JU1qRyIVqdbYokSLvkDIAfcokmYj1ebw8k,24104
26
26
  mdbq/spider/__init__.py,sha256=RBMFXGy_jd1HXZhngB2T2XTvJqki8P_Fr-pBcwijnew,18
27
27
  mdbq/spider/aikucun.py,sha256=OhyEv1VyAKTOHjLDM37iNDQeRg5OnrNoKODoG2VxHes,19806
28
- mdbq-3.9.9.dist-info/METADATA,sha256=F6RAyI8aGmpT-VLwVeY7jw13qemIce-PMH2Ri335GAE,363
29
- mdbq-3.9.9.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
30
- mdbq-3.9.9.dist-info/top_level.txt,sha256=2FQ-uLnCSB-OwFiWntzmwosW3X2Xqsg0ewh1axsaylA,5
31
- mdbq-3.9.9.dist-info/RECORD,,
28
+ mdbq-3.9.11.dist-info/METADATA,sha256=NuMxnqS57k-Z2opqI-yPWqkFPdTJqPJUaqZSwUDb1uY,364
29
+ mdbq-3.9.11.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
30
+ mdbq-3.9.11.dist-info/top_level.txt,sha256=2FQ-uLnCSB-OwFiWntzmwosW3X2Xqsg0ewh1axsaylA,5
31
+ mdbq-3.9.11.dist-info/RECORD,,
File without changes