mdbq 2.9.0__py3-none-any.whl → 2.9.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.
mdbq/mysql/mysql.py CHANGED
@@ -41,6 +41,63 @@ warnings.filterwarnings('ignore')
41
41
  """
42
42
 
43
43
 
44
+ def is_valid_date(date_string):
45
+ """ 判断是否是日期格式, 且允许没有前导零, 且允许带时间 """
46
+ date_pattern = r"^(\d{4})-(0?[1-9]|1[0-2])-(0?[1-9]|[12]\d|3[01])$"
47
+ match = re.match(date_pattern, str(date_string)) # 判断纯日期格式:2024-11-09
48
+ if match is None:
49
+ date_pattern = r".*\d+:\d+:\d+$"
50
+ match = re.match(date_pattern, date_string) # 判断日期+时间:2024-11-09 00:36:45
51
+ if match is not None:
52
+ return 2
53
+ else:
54
+ return 1
55
+
56
+
57
+ def is_integer(int_str):
58
+ """ 判断是否整数, 允许包含千分位分隔符, 允许科学计数法 """
59
+ # 如果是科学计数法
60
+ match = re.findall(r'^[-+]?(\d+)\.(\d+)[eE][-+]?(\d+)$', str(int_str))
61
+ if match:
62
+ if len(match[0]) == 3:
63
+ if int(match[0][0]) == 0: # 0 开头
64
+ if int(match[0][2]) > 10: # 转换后整数长度超过 10 位
65
+ return False
66
+ else: # 不是 0 开头
67
+ if len(match[0][0]) + int(match[0][2]) > 10: # 转换后整数长度超过 10 位
68
+ return False
69
+ if int(match[0][2]) >= len(match[0][1]):
70
+ return True
71
+ else:
72
+ return False
73
+ # 如果是普通数字, 且允许千分符
74
+ __pattern = r'^[-+]?\d{1,3}(,\d{3}){0,3}$|^[-+]?\d{1,9}$'
75
+ return re.match(__pattern, str(int_str)) is not None
76
+
77
+
78
+ def count_decimal_places(num_str):
79
+ """ 计算小数位数, 允许科学计数法 """
80
+ match = re.match(r'^[-+]?\d+(\.\d+)?([eE][-+]?\d+)?$', str(num_str))
81
+ if match:
82
+ # 如果是科学计数法
83
+ match = re.findall(r'(\d+)\.(\d+)[eE][-+]?(\d+)$', str(num_str))
84
+ if match:
85
+ if len(match[0]) == 3:
86
+ if int(match[0][2]) < len(match[0][1]):
87
+ # count_int 清除整数部分开头的 0 并计算整数位数
88
+ count_int = len(re.sub('^0+', '', str(match[0][0]))) + int(match[0][2])
89
+ # 计算小数位数
90
+ count_float = len(match[0][1]) - int(match[0][2])
91
+ return count_int, count_float
92
+ # 如果是普通小数
93
+ match = re.findall(r'(\d+)\.(\d+)$', str(num_str))
94
+ if match:
95
+ count_int = len(re.sub('^0+', '', str(match[0][0])))
96
+ count_float = len(match[0][1])
97
+ return count_int, count_float # 计算小数位数
98
+ return 0, 0
99
+
100
+
44
101
  class MysqlUpload:
45
102
  def __init__(self, username: str, password: str, host: str, port: int, charset: str = 'utf8mb4'):
46
103
  self.username = username
@@ -68,6 +125,127 @@ class MysqlUpload:
68
125
 
69
126
  return wrapper
70
127
 
128
+ def dict_to_mysql(self, db_name, table_name, dict_data, main_key=None, unique_main_key=None, index_length=100):
129
+ """
130
+ 插入字典数据
131
+ dict_data: 字典
132
+ main_key: 指定索引列
133
+ unique_main_key: 指定唯一索引列
134
+ index_length: 索引长度
135
+ """
136
+
137
+ if not main_key:
138
+ main_key = []
139
+ if not unique_main_key:
140
+ unique_main_key = []
141
+ connection = pymysql.connect(**self.config) # 连接数据库
142
+ with connection.cursor() as cursor:
143
+ cursor.execute(f"SHOW DATABASES LIKE '{db_name}'") # 检查数据库是否存在
144
+ database_exists = cursor.fetchone()
145
+ if not database_exists:
146
+ # 如果数据库不存在,则新建
147
+ if '8.138.27' in str(self.host) or platform.system() == "Linux": # 阿里云 mysql 低版本不支持 0900
148
+ sql = f"CREATE DATABASE `{db_name}` COLLATE utf8mb4_unicode_ci"
149
+ self.config.update({'charset': 'utf8mb4_unicode_ci'})
150
+ if '192.168.1.100' in str(self.host):
151
+ sql = f"CREATE DATABASE `{db_name}`"
152
+ else:
153
+ sql = f"CREATE DATABASE `{db_name}` COLLATE utf8mb4_0900_ai_ci"
154
+ cursor.execute(sql)
155
+ connection.commit()
156
+ print(f"创建Database: {db_name}")
157
+
158
+ self.config.update({'database': db_name}) # 添加更新 config 字段
159
+ connection = pymysql.connect(**self.config) # 重新连接数据库
160
+ with connection.cursor() as cursor:
161
+ # 1. 查询表, 不存在则创建一个空表
162
+ sql = "SHOW TABLES LIKE %s;" # 有特殊字符不需转义
163
+ cursor.execute(sql, (table_name))
164
+ if not cursor.fetchone():
165
+ sql = f"CREATE TABLE IF NOT EXISTS `{table_name}` (id INT AUTO_INCREMENT PRIMARY KEY);"
166
+ cursor.execute(sql)
167
+ print(f'创建 mysql 表: {table_name}')
168
+
169
+ # 根据 dict_data 的值添加指定的数据类型
170
+ dtypes = self.cover_dict_dtypes(dict_data=dict_data) # {'店铺名称': 'mediumtext',...}
171
+ # 检查列
172
+ sql = "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s;"
173
+ cursor.execute(sql, (db_name, table_name))
174
+ col_exist = [item['COLUMN_NAME'] for item in cursor.fetchall()] # 已存在的所有列
175
+ col_not_exist = [col for col in dict_data.keys() if col not in col_exist] # 不存在的列
176
+ # 不存在则新建列
177
+ if col_not_exist: # 数据表中不存在的列
178
+ for col in col_not_exist:
179
+ # 创建列,需转义
180
+ sql = f"ALTER TABLE `{table_name}` ADD COLUMN `{col}` {dtypes[col]} NOT NULL;"
181
+ cursor.execute(sql)
182
+ print(f"添加列: {col}({dtypes[col]})") # 添加列并指定数据类型
183
+
184
+ if col in main_key or col in unique_main_key:
185
+ sql = f"SHOW INDEXES FROM `{table_name}` WHERE `Column_name` = %s"
186
+ cursor.execute(sql, (col))
187
+ result = cursor.fetchone() # 检查索引是否存在
188
+ if not result:
189
+ if col in main_key:
190
+ cursor.execute(f"CREATE INDEX index_name ON `{table_name}`(`{col}`({index_length}))")
191
+ print(f"创建索引列: {col}({dtypes[col]})") # 添加列并指定数据类型
192
+ elif col in unique_main_key:
193
+ cursor.execute(f"CREATE UNIQUE INDEX index_name ON `{table_name}`(`{col}`({index_length}))")
194
+ print(f"创建唯一索引: {col}({dtypes[col]})") # 添加列并指定数据类型
195
+ connection.commit() # 提交事务
196
+
197
+ keys_data = ', '.join([f'`{str(item)}`' for item in dict_data.keys()])
198
+ values_data = ', '.join(f'"{str(item)}"' for item in dict_data.values())
199
+ del dict_data['客户id']
200
+ update_datas = ', '.join([f'{k} = VALUES({k})' for k, v in dict_data.items()])
201
+ # print(update_datas)
202
+ sql = f"INSERT INTO %s (%s) VALUES (%s) ON DUPLICATE KEY UPDATE %s" % (table_name, keys_data, values_data, update_datas)
203
+ # print(sql)
204
+ cursor.execute(sql)
205
+ connection.commit() # 提交事务
206
+ connection.close()
207
+
208
+
209
+ def cover_dict_dtypes(self, dict_data):
210
+ if not dict_data:
211
+ print(f'mysql.py -> MysqlUpload -> cover_dict_dtypes -> 传入的字典不能为空')
212
+ return
213
+ __res_dict = {}
214
+ for k, v in dict_data.items():
215
+ result1 = re.findall(r'商品编码|[商品宝贝]_?id|s[kp]u_?id|货号|款号|文件大小', str(k), re.IGNORECASE)
216
+ result2 = re.findall(r'占比|投产|产出|同比|环比|roi$', str(k), re.IGNORECASE)
217
+ date_type = is_valid_date(str(v)) # 判断日期时间
218
+ int_num = is_integer(str(v)) # 判断整数
219
+ count_int, count_float = count_decimal_places(str(v)) # 判断小数,返回小数位数
220
+ if result1: # 京东sku/spu商品信息
221
+ __res_dict.update({k: 'mediumtext'})
222
+ elif result2:
223
+ __res_dict.update({k: 'decimal(10,2)'})
224
+ elif str(v) == '' or str(v).lower() == 'nan' or str(v).lower() == 'null':
225
+ v = 0
226
+ dict_data.update({k: v})
227
+ __res_dict.update({k: 'mediumtext'})
228
+ elif date_type == 1:
229
+ __res_dict.update({k: 'DATE'})
230
+ elif date_type == 2:
231
+ __res_dict.update({k: 'DATETIME'})
232
+ elif int_num:
233
+ __res_dict.update({k: 'INT'})
234
+ elif count_float > 0:
235
+ if count_int + count_float > 10:
236
+ __res_dict.update({k: 'mediumtext'})
237
+ elif count_float >= 6:
238
+ __res_dict.update({k: 'decimal(12,6)'})
239
+ elif count_float >= 4:
240
+ __res_dict.update({k: 'decimal(10,4)'})
241
+ else:
242
+ __res_dict.update({k: 'decimal(10,2)'})
243
+ else:
244
+ __res_dict.update({k: 'mediumtext'})
245
+ return __res_dict
246
+
247
+
248
+
71
249
  @try_except
72
250
  def df_to_mysql(self, df, table_name, db_name='远程数据源', icm_update=[], service_database={'xigua_lx': 'mysql'}, move_insert=False, df_sql=False, drop_duplicates=False, filename=None, count=None, json_path=None, reset_id=False):
73
251
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mdbq
3
- Version: 2.9.0
3
+ Version: 2.9.2
4
4
  Home-page: https://pypi.org/project/mdbq
5
5
  Author: xigua,
6
6
  Author-email: 2587125111@qq.com
@@ -28,7 +28,7 @@ mdbq/log/mylogger.py,sha256=oaT7Bp-Hb9jZt52seP3ISUuxVcI19s4UiqTeouScBO0,3258
28
28
  mdbq/mongo/__init__.py,sha256=SILt7xMtQIQl_m-ik9WLtJSXIVf424iYgCfE_tnQFbw,13
29
29
  mdbq/mongo/mongo.py,sha256=v9qvrp6p1ZRWuPpbSilqveiE0FEcZF7U5xUPI0RN4xs,31880
30
30
  mdbq/mysql/__init__.py,sha256=A_DPJyAoEvTSFojiI2e94zP0FKtCkkwKP1kYUCSyQzo,11
31
- mdbq/mysql/mysql.py,sha256=apcj0WDdbrHr7UzO2kjcesDxDUlWxG4KcIpI1mBuwMk,46152
31
+ mdbq/mysql/mysql.py,sha256=jq2_lQFTnQR6N6QdSqxKqjennazITh5TdTg6j7oerYE,55006
32
32
  mdbq/mysql/recheck_mysql.py,sha256=jHQSlQy0PlQ_EYICQv_2nairUX3t6OIwPtSELKIpjkY,8702
33
33
  mdbq/mysql/s_query.py,sha256=bgNNIqYLDCHjD5KTFcm6x4u74selpAGs5ouJYuqX86k,8447
34
34
  mdbq/mysql/year_month_day.py,sha256=VgewoE2pJxK7ErjfviL_SMTN77ki8GVbTUcao3vFUCE,1523
@@ -45,7 +45,7 @@ mdbq/req_post/__init__.py,sha256=jso1oHcy6cJEfa7udS_9uO5X6kZLoPBF8l3wCYmr5dM,18
45
45
  mdbq/req_post/req_tb.py,sha256=PexWSCPJNM6Tv0ol4lAWIhlOwsAr_frnjtcdSHCFiek,36179
46
46
  mdbq/spider/__init__.py,sha256=RBMFXGy_jd1HXZhngB2T2XTvJqki8P_Fr-pBcwijnew,18
47
47
  mdbq/spider/aikucun.py,sha256=jHrdGWBJQaSywx7V-U4YuM6vWkwC5SR5tTOOdB3YU_c,17306
48
- mdbq-2.9.0.dist-info/METADATA,sha256=s0ZT23hnWNpOZ5YBPTKigR8XftMlBOTCzH_7Lfuamig,243
49
- mdbq-2.9.0.dist-info/WHEEL,sha256=eOLhNAGa2EW3wWl_TU484h7q1UNgy0JXjjoqKoxAAQc,92
50
- mdbq-2.9.0.dist-info/top_level.txt,sha256=2FQ-uLnCSB-OwFiWntzmwosW3X2Xqsg0ewh1axsaylA,5
51
- mdbq-2.9.0.dist-info/RECORD,,
48
+ mdbq-2.9.2.dist-info/METADATA,sha256=3_u-jNgpFq2qX6uv2ufoYgPirGomwoz9IZkYmRp1BIc,243
49
+ mdbq-2.9.2.dist-info/WHEEL,sha256=cpQTJ5IWu9CdaPViMhC9YzF8gZuS5-vlfoFihTBC86A,91
50
+ mdbq-2.9.2.dist-info/top_level.txt,sha256=2FQ-uLnCSB-OwFiWntzmwosW3X2Xqsg0ewh1axsaylA,5
51
+ mdbq-2.9.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.44.0)
2
+ Generator: setuptools (70.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5