mdbq 3.9.4__py3-none-any.whl → 3.9.6__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/__version__.py CHANGED
@@ -1 +1 @@
1
- VERSION = '3.9.4'
1
+ VERSION = '3.9.6'
mdbq/log/mylogger.py ADDED
@@ -0,0 +1,556 @@
1
+ import logging
2
+ import logging.handlers
3
+ import datetime
4
+ import json
5
+ import os
6
+ import sys
7
+ import time
8
+ import threading
9
+ import queue
10
+ from typing import Optional, Dict, Any, List, Callable, Union
11
+ import atexit
12
+
13
+ try:
14
+ import psutil
15
+
16
+ HAS_PSUTIL = True
17
+ except ImportError:
18
+ HAS_PSUTIL = False
19
+
20
+
21
+ class MyLogger:
22
+ """
23
+ 增强版结构化日志记录器,支持多种高级功能
24
+
25
+ 功能增强:
26
+ - 异步日志记录(减少I/O阻塞)
27
+ - 上下文管理器支持
28
+ - 自定义日志过滤器
29
+ - 更丰富的系统指标采集
30
+ - 日志采样控制
31
+ - 动态日志级别调整
32
+ - 请求跟踪ID
33
+ - 多线程安全
34
+ - 日志缓冲和批量写入
35
+ - 自定义异常处理
36
+
37
+ 使用示例:
38
+ logger = MyLogger(
39
+ name='app_logger',
40
+ logging_mode='both',
41
+ log_level='INFO',
42
+ log_file='app.log',
43
+ max_log_size=50,
44
+ backup_count=5,
45
+ enable_async=True
46
+ )
47
+
48
+ with logger.context(request_id='12345'):
49
+ logger.info("处理请求", extra={'user': 'admin'})
50
+ """
51
+
52
+ def __init__(
53
+ self,
54
+ name: str = 'mylogger.log',
55
+ logging_mode: str = 'console', # 'both', 'console', 'file', 'none'
56
+ log_level: str = 'INFO', # 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'
57
+ log_file: str = 'm_app.log',
58
+ log_format: str = 'json', # 默认json格式,可选'simple'
59
+ max_log_size: int = 50, # MB
60
+ backup_count: int = 5,
61
+ sensitive_fields: Optional[List[str]] = None,
62
+ enable_async: bool = False,
63
+ buffer_size: int = 1000,
64
+ sample_rate: float = 1.0,
65
+ filters: Optional[List[Callable]] = None,
66
+ enable_metrics: bool = False,
67
+ metrics_interval: int = 300,
68
+ message_limited: int = 1000
69
+ ):
70
+ """
71
+ 初始化日志记录器
72
+
73
+ :param name: 日志记录器名称
74
+ :param logging_mode: 日志输出模式
75
+ :param log_level: 日志级别
76
+ :param log_file: 日志文件路径
77
+ :param max_log_size: 单个日志文件最大大小(MB)
78
+ :param backup_count: 保留的日志文件数量
79
+ :param sensitive_fields: 敏感字段列表(会被过滤)
80
+ :param enable_async: 是否启用异步日志
81
+ :param buffer_size: 日志缓冲大小(仅异步模式有效)
82
+ :param sample_rate: 控制日志的采样率(0.0-1.0),使用消息内容的哈希值来决定是否记录,减少日志量,防止日志过于冗长
83
+ :param filters: 自定义日志过滤器列表
84
+ :param enable_metrics: 是否启用系统指标采集
85
+ :param metrics_interval: 指标采集间隔(秒)
86
+ :param message_limited: 简化日志内容,避免过长
87
+ """
88
+ log_path = os.path.join(os.path.expanduser("~"), 'logfile')
89
+ if not os.path.isdir(log_path):
90
+ os.makedirs(log_path)
91
+
92
+ self.name = name
93
+ self.logging_mode = logging_mode.lower()
94
+ self.log_level = log_level.upper()
95
+ self.log_file = os.path.join(log_path, log_file)
96
+ self.log_format = log_format
97
+ self.max_log_size = max_log_size
98
+ self.backup_count = backup_count
99
+ self.sensitive_fields = sensitive_fields or []
100
+ self.enable_async = enable_async
101
+ self.buffer_size = buffer_size
102
+ self.sample_rate = max(0.0, min(1.0, sample_rate))
103
+ self.filters = filters or []
104
+ self.enable_metrics = enable_metrics and HAS_PSUTIL
105
+ self.metrics_interval = metrics_interval
106
+ self.message_limited = message_limited
107
+
108
+ # 上下文相关
109
+ self._context = threading.local()
110
+ self._context.data = {}
111
+
112
+ # 系统指标相关
113
+ self._last_metrics_time = 0
114
+ self._metrics_cache = {}
115
+
116
+ # 异步日志相关
117
+ self._log_queue = queue.Queue(maxsize=buffer_size)
118
+ self._async_thread = None
119
+ self._stop_event = threading.Event()
120
+
121
+ # 创建日志记录器
122
+ self.logger = logging.getLogger(name)
123
+ self._init_logging()
124
+
125
+ if self.enable_async:
126
+ self._start_async_logging()
127
+
128
+ atexit.register(self.shutdown)
129
+
130
+ def __enter__(self):
131
+ """上下文管理器入口"""
132
+ return self
133
+
134
+ def __exit__(self, exc_type, exc_val, exc_tb):
135
+ """上下文管理器退出"""
136
+ self.shutdown()
137
+ if exc_type is not None:
138
+ self.error(f"上下文内异常: {exc_val}",
139
+ extra={'类型': str(exc_type)})
140
+ return False
141
+
142
+ def context(self, **kwargs):
143
+ """返回一个上下文管理器,可以设置临时上下文变量"""
144
+ return self._ContextManager(self, kwargs)
145
+
146
+ class _ContextManager:
147
+ def __init__(self, logger, context_vars):
148
+ self.logger = logger
149
+ self.context_vars = context_vars
150
+ self.old_context = {}
151
+
152
+ def __enter__(self):
153
+ # 保存旧上下文并设置新上下文
154
+ self.old_context = getattr(self.logger._context, 'data', {}).copy()
155
+ self.logger._context.data.update(self.context_vars)
156
+ return self.logger
157
+
158
+ def __exit__(self, exc_type, exc_val, exc_tb):
159
+ # 恢复旧上下文
160
+ self.logger._context.data = self.old_context
161
+ if exc_type is not None:
162
+ self.logger.error(f"上下文内异常2: {exc_val}",
163
+ extra={'类型': str(exc_type)})
164
+ return False
165
+
166
+ def _init_logging(self):
167
+ """初始化日志配置"""
168
+ valid_levels = ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']
169
+ if self.log_level not in valid_levels:
170
+ self.log_level = 'INFO'
171
+
172
+ self.logger.setLevel(self.log_level)
173
+
174
+ # 防止重复添加handler
175
+ if self.logger.handlers:
176
+ for handler in self.logger.handlers[:]:
177
+ self.logger.removeHandler(handler)
178
+
179
+ # 定义日志格式
180
+ if self.log_format.lower() == 'simple':
181
+ # 简单文本格式
182
+ class SimpleFormatter(logging.Formatter):
183
+ def format(self, record):
184
+ # 基础日志信息
185
+ msg = super().format(record)
186
+
187
+ # 添加上下文信息
188
+ if hasattr(record, 'extra_data') and record.extra_data:
189
+ context_data = record.extra_data.get('context_data', {})
190
+ if context_data:
191
+ msg += f" | Context: {context_data}"
192
+
193
+ # 添加性能指标
194
+ metrics = record.extra_data.get('性能指标', {})
195
+ if metrics:
196
+ msg += f" | Metrics: {metrics}"
197
+
198
+ # 添加其他额外数据
199
+ extra = {k: v for k, v in record.extra_data.items()
200
+ if k not in ('context_data', '性能指标')}
201
+ if extra:
202
+ msg += f" | Extra: {extra}"
203
+
204
+ return msg
205
+
206
+ formatter = SimpleFormatter('%(asctime)s - %(levelname)s - %(message)s')
207
+ formatter.datefmt = '%Y-%m-%d %H:%M:%S'
208
+ else:
209
+ # 结构化JSON格式
210
+ class StructuredFormatter(logging.Formatter):
211
+ def format(self, record):
212
+ log_data = {
213
+ 'timestamp': datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
214
+ 'level': record.levelname,
215
+ 'message': record.getMessage(),
216
+ # 'module': record.module,
217
+ 'function': record.funcName,
218
+ # 'line': record.lineno,
219
+ # 'thread': record.threadName,
220
+ # 'process': record.processName,
221
+ 'name': record.name,
222
+ }
223
+
224
+ # 添加额外字段
225
+ if hasattr(record, 'extra_data'):
226
+ log_data.update(record.extra_data)
227
+
228
+ # 添加上下文信息
229
+ if hasattr(record, 'context_data'):
230
+ log_data.update(record.context_data)
231
+
232
+ # 添加异常信息
233
+ if record.exc_info:
234
+ log_data['异常'] = self.formatException(record.exc_info)
235
+
236
+ # 过滤敏感信息
237
+ if hasattr(record, '过滤'):
238
+ for field in record.sensitive_fields:
239
+ if field in log_data:
240
+ log_data[field] = '***'
241
+ if isinstance(log_data.get('message'), str):
242
+ log_data['message'] = log_data['message'].replace(field, '***')
243
+
244
+ return json.dumps(log_data, ensure_ascii=False)
245
+
246
+ formatter = StructuredFormatter()
247
+
248
+ # 根据模式添加相应的handler
249
+ if self.logging_mode in ('both', 'console'):
250
+ console_handler = logging.StreamHandler()
251
+ console_handler.setFormatter(formatter)
252
+ self.logger.addHandler(console_handler)
253
+
254
+ if self.logging_mode in ('both', 'file'):
255
+ # 确保日志目录存在
256
+ log_dir = os.path.dirname(self.log_file)
257
+ if log_dir and not os.path.exists(log_dir):
258
+ os.makedirs(log_dir)
259
+
260
+ file_handler = logging.handlers.RotatingFileHandler(
261
+ filename=self.log_file,
262
+ maxBytes=self.max_log_size * 1024 * 1024,
263
+ backupCount=self.backup_count,
264
+ encoding='utf-8',
265
+ delay=False
266
+ )
267
+ file_handler.setFormatter(formatter)
268
+ self.logger.addHandler(file_handler)
269
+
270
+ def _get_system_metrics(self) -> Dict[str, Any]:
271
+ """获取系统资源使用指标"""
272
+ if not self.enable_metrics:
273
+ return {}
274
+
275
+ try:
276
+ return {
277
+ '内存': {
278
+ '使用率': psutil.virtual_memory().percent,
279
+ '已使用': psutil.virtual_memory().used,
280
+ '可用': psutil.virtual_memory().available,
281
+ },
282
+ 'CPU': {
283
+ '使用率': psutil.cpu_percent(),
284
+ '核心数': psutil.cpu_count(),
285
+ },
286
+ '磁盘': {
287
+ '使用率': psutil.disk_usage('/').percent,
288
+ '已使用': psutil.disk_usage('/').used,
289
+ '剩余': psutil.disk_usage('/').free,
290
+ },
291
+ '网络': {
292
+ '发送字节数': psutil.net_io_counters().bytes_sent,
293
+ '接收字节数': psutil.net_io_counters().bytes_recv,
294
+ },
295
+ '进程': {
296
+ 'PID': os.getpid(),
297
+ '线程数': threading.active_count(),
298
+ }
299
+ }
300
+ except Exception as e:
301
+ self.logger.warning(f"无法采集系统性能指标: {e}",
302
+ extra={'extra_data': {'metrics_error': str(e)}})
303
+ return {}
304
+
305
+ def _apply_filters(self, level: str, message: str, extra: Dict) -> bool:
306
+ """应用自定义过滤器"""
307
+ for filter_func in self.filters:
308
+ try:
309
+ if not filter_func(level, message, extra):
310
+ return False # 如果过滤器返回 False,则丢弃该日志
311
+ except Exception as e:
312
+ self.logger.warning(f"过滤失败: {e}",
313
+ extra={'extra_data': {'filter_error': str(e)}})
314
+ return True # 所有过滤器都返回 True,则记录该日志
315
+
316
+ def _async_log_worker(self):
317
+ """异步日志工作线程"""
318
+ while not self._stop_event.is_set() or not self._log_queue.empty():
319
+ try:
320
+ log_args = self._log_queue.get(timeout=0.1)
321
+ if log_args:
322
+ level, message, extra = log_args
323
+ self._sync_log(level, message, extra)
324
+ except queue.Empty:
325
+ continue
326
+ except Exception as e:
327
+ # 防止日志线程崩溃
328
+ try:
329
+ self.logger.error(f"日志线程崩溃: {e}",
330
+ extra={'extra_data': {'async_error': str(e)}})
331
+ except:
332
+ pass
333
+
334
+ def _start_async_logging(self):
335
+ """启动异步日志线程"""
336
+ self._stop_event.clear()
337
+ self._async_thread = threading.Thread(
338
+ target=self._async_log_worker,
339
+ name=f"{self.name}_async_logger",
340
+ daemon=True
341
+ )
342
+ self._async_thread.start()
343
+
344
+ def _sync_log(self, level: str, message: str, extra: Optional[Dict] = None):
345
+ """同步日志记录"""
346
+ if not hasattr(self.logger, level.lower()):
347
+ return
348
+
349
+ # 简化日志内容,避免过长
350
+ if len(message) > self.message_limited:
351
+ message = message[:self.message_limited] + '...'
352
+
353
+ # 定期收集系统指标
354
+ if self.enable_metrics:
355
+ now = time.time()
356
+ if now - self._last_metrics_time > self.metrics_interval:
357
+ self._metrics_cache = self._get_system_metrics()
358
+ self._last_metrics_time = now
359
+
360
+ # 准备日志额外数据
361
+ log_extra = {}
362
+ if self.enable_metrics:
363
+ log_extra['性能指标'] = self._metrics_cache
364
+ if extra:
365
+ log_extra.update(extra)
366
+
367
+ # 添加上下文信息
368
+ if hasattr(self._context, 'data') and self._context.data:
369
+ log_extra['context_data'] = self._context.data.copy()
370
+
371
+ # 添加敏感字段过滤
372
+ log_extra['过滤'] = self.sensitive_fields
373
+
374
+ # 应用日志采样
375
+ if self.sample_rate < 1.0 and level.lower() in ('debug', 'info'):
376
+ if hash(message) % 100 >= self.sample_rate * 100:
377
+ return
378
+
379
+ # 应用过滤器
380
+ if not self._apply_filters(level, message, log_extra):
381
+ return
382
+
383
+ # 记录日志
384
+ getattr(self.logger, level.lower())(message, extra={'extra_data': log_extra})
385
+
386
+ def log(self, level: str, message: str, extra: Optional[Dict] = None):
387
+ """
388
+ 记录日志
389
+
390
+ :param level: 日志级别 ('debug', 'info', 'warning', 'error', 'critical')
391
+ :param message: 日志消息
392
+ :param extra: 额外数据字典
393
+ """
394
+ if not hasattr(self.logger, level.lower()):
395
+ return
396
+
397
+ if self.enable_async:
398
+ try:
399
+ self._log_queue.put((level, message, extra), timeout=0.1)
400
+ except queue.Full:
401
+ # 队列满时降级为同步日志
402
+ self._sync_log(level, f"[ASYNC QUEUE FULL] {message}", extra)
403
+ else:
404
+ self._sync_log(level, message, extra)
405
+
406
+ def set_level(self, level: str):
407
+ """动态设置日志级别"""
408
+ valid_levels = ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']
409
+ level = level.upper()
410
+ if level in valid_levels:
411
+ self.log_level = level
412
+ self.logger.setLevel(level)
413
+ for handler in self.logger.handlers:
414
+ handler.setLevel(level)
415
+
416
+ def add_filter(self, filter_func: Callable):
417
+ """添加日志过滤器"""
418
+ if callable(filter_func):
419
+ self.filters.append(filter_func)
420
+
421
+ def set_context(self, **kwargs):
422
+ """设置上下文变量"""
423
+ if not hasattr(self._context, 'data'):
424
+ self._context.data = {}
425
+ self._context.data.update(kwargs)
426
+
427
+ def get_context(self, key: str, default=None):
428
+ """获取上下文变量"""
429
+ if hasattr(self._context, 'data'):
430
+ return self._context.data.get(key, default)
431
+ return default
432
+
433
+ def clear_context(self):
434
+ """清除所有上下文变量"""
435
+ if hasattr(self._context, 'data'):
436
+ self._context.data.clear()
437
+
438
+ def shutdown(self):
439
+ """关闭日志记录器,确保所有日志被刷新"""
440
+ if self.enable_async:
441
+ self._stop_event.set()
442
+ if self._async_thread and self._async_thread.is_alive():
443
+ self._async_thread.join(timeout=5)
444
+
445
+ # 确保所有handler被刷新
446
+ for handler in self.logger.handlers:
447
+ handler.flush()
448
+ handler.close()
449
+
450
+ def debug(self, message: str, extra: Optional[Dict] = None):
451
+ """记录调试信息"""
452
+ self.log('debug', message, extra)
453
+
454
+ def info(self, message: str, extra: Optional[Dict] = None):
455
+ """记录一般信息"""
456
+ self.log('info', message, extra)
457
+
458
+ def warning(self, message: str, extra: Optional[Dict] = None):
459
+ """记录警告信息"""
460
+ self.log('warning', message, extra)
461
+
462
+ def error(self, message: str, extra: Optional[Dict] = None):
463
+ """记录错误信息"""
464
+ self.log('error', message, extra)
465
+
466
+ def critical(self, message: str, extra: Optional[Dict] = None):
467
+ """记录严重错误信息"""
468
+ self.log('critical', message, extra)
469
+
470
+ def exception(self, message: str, exc_info: Exception, extra: Optional[Dict] = None):
471
+ """记录异常信息"""
472
+ if not extra:
473
+ extra = {}
474
+ # # 获取异常发生的实际位置
475
+ # tb = exc_info.__traceback__
476
+ #
477
+ # if tb:
478
+ # extra.update({
479
+ # 'module': tb.tb_frame.f_globals.get('__name__', ''),
480
+ # 'function': tb.tb_frame.f_code.co_name,
481
+ # 'line': tb.tb_lineno,
482
+ # 'file': tb.tb_frame.f_code.co_filename
483
+ # })
484
+ # extra['异常'] = str(exc_info)
485
+ # extra['类型'] = exc_info.__class__.__name__
486
+ # self.log('error', message, extra)
487
+
488
+ # 获取完整的异常堆栈
489
+ tb = exc_info.__traceback__
490
+ while tb.tb_next:
491
+ tb = tb.tb_next # 获取最内层的堆栈帧
492
+
493
+ extra.update({
494
+ 'module': tb.tb_frame.f_globals.get('__name__', ''),
495
+ 'function': tb.tb_frame.f_code.co_name,
496
+ 'line': tb.tb_lineno,
497
+ 'file': tb.tb_frame.f_code.co_filename,
498
+ '异常': str(exc_info),
499
+ '类型': exc_info.__class__.__name__,
500
+ '堆栈': self._format_traceback(exc_info)
501
+ })
502
+
503
+ # 直接使用logger的error方法记录,保留原始调用栈
504
+ self.log('error', message, extra)
505
+
506
+ def _format_traceback(self, exc_info):
507
+ """格式化异常堆栈"""
508
+ import traceback
509
+ return ''.join(traceback.format_exception(type(exc_info), exc_info, exc_info.__traceback__))
510
+
511
+ def timeit(self, message: str = "Execution time"):
512
+ """返回一个计时器上下文管理器"""
513
+ return self._Timer(self, message)
514
+
515
+ class _Timer:
516
+ def __init__(self, logger, message):
517
+ self.logger = logger
518
+ self.message = message
519
+ self.start_time = None
520
+
521
+ def __enter__(self):
522
+ self.start_time = time.time()
523
+ return self
524
+
525
+ def __exit__(self, exc_type, exc_val, exc_tb):
526
+ elapsed = time.time() - self.start_time
527
+ self.logger.info(f"{self.message}: {elapsed:.3f}s",
528
+ extra={'elapsed_seconds': elapsed})
529
+ return False
530
+
531
+
532
+ def main():
533
+ # 创建日志记录器
534
+ logger = MyLogger(
535
+ name='my_app',
536
+ logging_mode='both',
537
+ log_level='DEBUG',
538
+ log_file='/Users/xigua/Downloads/my_app.log',
539
+ log_format='json',
540
+ max_log_size=50,
541
+ backup_count=5,
542
+ enable_async=True, # 是否启用异步日志
543
+ sample_rate=1, # 采样50%的DEBUG/INFO日志
544
+ sensitive_fields=[], # 敏感字段列表
545
+ enable_metrics=False, # 是否启用性能指标
546
+ )
547
+
548
+ logger.info('123')
549
+
550
+ # 确保所有日志被刷新
551
+ logger.shutdown()
552
+
553
+
554
+ if __name__ == '__main__':
555
+ pass
556
+ main()
mdbq/mysql/mysql.py CHANGED
@@ -3004,7 +3004,7 @@ class MySQLDeduplicator:
3004
3004
  def main():
3005
3005
  uploader = MySQLUploader(
3006
3006
  username='root',
3007
- password='1',
3007
+ password='188988yang188',
3008
3008
  host='localhost',
3009
3009
  port=3306,
3010
3010
  logging_mode='console',
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: mdbq
3
- Version: 3.9.4
3
+ Version: 3.9.6
4
4
  Home-page: https://pypi.org/project/mdbq
5
5
  Author: xigua,
6
6
  Author-email: 2587125111@qq.com
@@ -1,14 +1,15 @@
1
1
  mdbq/__init__.py,sha256=Il5Q9ATdX8yXqVxtP_nYqUhExzxPC_qk_WXQ_4h0exg,16
2
- mdbq/__version__.py,sha256=44Qvc6l4hjIIQuGixaBICZNZB9jeL2ztNkT4fkONEBc,17
2
+ mdbq/__version__.py,sha256=rCARLRw4aYsH5Ac1n95qGJBlNNqAlv-EapPM3bcYkto,17
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
6
6
  mdbq/config/__init__.py,sha256=jso1oHcy6cJEfa7udS_9uO5X6kZLoPBF8l3wCYmr5dM,18
7
7
  mdbq/config/config.py,sha256=eaTfrfXQ65xLqjr5I8-HkZd_jEY1JkGinEgv3TSLeoQ,3170
8
8
  mdbq/log/__init__.py,sha256=Mpbrav0s0ifLL7lVDAuePEi1hJKiSHhxcv1byBKDl5E,15
9
+ mdbq/log/mylogger.py,sha256=CeQf8RcVITm9bVr-hHYdx6QtIbA--YkmWBAaPcMcXnY,20724
9
10
  mdbq/log/spider_logging.py,sha256=-ozWWEGm3HVv604ozs_OOvVwumjokmUPwbaodesUrPY,1664
10
11
  mdbq/mysql/__init__.py,sha256=A_DPJyAoEvTSFojiI2e94zP0FKtCkkwKP1kYUCSyQzo,11
11
- mdbq/mysql/mysql.py,sha256=ylGvSzFE2B78y77wG266tf_RaEuETnngqDKUTqjQCjs,132378
12
+ mdbq/mysql/mysql.py,sha256=WFkv2flWuYRJqTzCOvxyiPeJgyJvVQzSG24jH_x5oEg,132390
12
13
  mdbq/mysql/s_query.py,sha256=X055aLRAgxVvueXx4NbfNjp6MyBI02_XBb1pTKw09L0,8660
13
14
  mdbq/other/__init__.py,sha256=jso1oHcy6cJEfa7udS_9uO5X6kZLoPBF8l3wCYmr5dM,18
14
15
  mdbq/other/download_sku_picture.py,sha256=YU8DxKMXbdeE1OOKEA848WVp62jYHw5O4tXTjUdq9H0,44832
@@ -22,7 +23,7 @@ mdbq/redis/__init__.py,sha256=YtgBlVSMDphtpwYX248wGge1x-Ex_mMufz4-8W0XRmA,12
22
23
  mdbq/redis/getredis.py,sha256=Uk8-cOWT0JU1qRyIVqdbYokSLvkDIAfcokmYj1ebw8k,24104
23
24
  mdbq/spider/__init__.py,sha256=RBMFXGy_jd1HXZhngB2T2XTvJqki8P_Fr-pBcwijnew,18
24
25
  mdbq/spider/aikucun.py,sha256=OhyEv1VyAKTOHjLDM37iNDQeRg5OnrNoKODoG2VxHes,19806
25
- mdbq-3.9.4.dist-info/METADATA,sha256=1FQB3vRRNlxontQEXd6gE-RhnHbjAPOZcnc_Xh9I4B0,363
26
- mdbq-3.9.4.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
27
- mdbq-3.9.4.dist-info/top_level.txt,sha256=2FQ-uLnCSB-OwFiWntzmwosW3X2Xqsg0ewh1axsaylA,5
28
- mdbq-3.9.4.dist-info/RECORD,,
26
+ mdbq-3.9.6.dist-info/METADATA,sha256=RyMeBh6UL0QDEClon62yAzpowavd-5sUZKjniEex-hs,363
27
+ mdbq-3.9.6.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
28
+ mdbq-3.9.6.dist-info/top_level.txt,sha256=2FQ-uLnCSB-OwFiWntzmwosW3X2Xqsg0ewh1axsaylA,5
29
+ mdbq-3.9.6.dist-info/RECORD,,
File without changes