mdbq 4.0.35__py3-none-any.whl → 4.0.37__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 = '4.0.35'
1
+ VERSION = '4.0.37'
mdbq/log/mylogger.py CHANGED
@@ -12,6 +12,7 @@ import atexit
12
12
  import traceback
13
13
  import inspect
14
14
  import psutil
15
+ import multiprocessing
15
16
 
16
17
 
17
18
  def get_caller_filename(default='mylogger'):
@@ -70,7 +71,8 @@ class MyLogger:
70
71
  enable_metrics: bool = False,
71
72
  metrics_interval: int = 300,
72
73
  message_limited: int = 1000,
73
- flush_interval: int = 5
74
+ flush_interval: int = 5,
75
+ enable_multiprocess: bool = False
74
76
  ):
75
77
  """
76
78
  初始化日志器
@@ -90,6 +92,7 @@ class MyLogger:
90
92
  :param metrics_interval: 指标采集间隔(秒)
91
93
  :param message_limited: 简化日志内容,避免过长
92
94
  :param flush_interval: 定时刷新日志器间隔(秒)
95
+ :param enable_multiprocess: 是否启用多进程安全日志
93
96
  """
94
97
  log_path = os.path.join(os.path.expanduser("~"), 'logfile')
95
98
  if name is None:
@@ -113,8 +116,14 @@ class MyLogger:
113
116
  self.filters = filters or []
114
117
  self.enable_metrics = enable_metrics
115
118
  self.metrics_interval = metrics_interval
116
- self.message_limited = message_limited
117
- self.flush_interval = flush_interval
119
+ self.message_limited = max(1, int(message_limited))
120
+ self.flush_interval = max(1, int(flush_interval))
121
+ self.enable_multiprocess = enable_multiprocess
122
+ self._mp_queue = None
123
+ self._mp_writer_process = None
124
+ self._is_main_process = multiprocessing.current_process().name == 'MainProcess'
125
+ self._stop_event = threading.Event()
126
+ self._flush_thread = None
118
127
 
119
128
  # 上下文相关
120
129
  self._context = threading.local()
@@ -133,7 +142,9 @@ class MyLogger:
133
142
  self.logger = logging.getLogger(name)
134
143
  self._init_logging()
135
144
 
136
- if self.enable_async:
145
+ if self.enable_multiprocess:
146
+ self._setup_multiprocess_logging()
147
+ elif self.enable_async:
137
148
  self._setup_async_logging()
138
149
 
139
150
  atexit.register(self.shutdown)
@@ -184,14 +195,18 @@ class MyLogger:
184
195
  class SimpleFormatter(logging.Formatter):
185
196
  def format(self, record):
186
197
  msg = super().format(record)
187
- if hasattr(record, 'extra_data') and record.extra_data:
188
- context_data = record.extra_data.get('context_data', {})
198
+ # 统一处理 extra_data 字段
199
+ extra_data = getattr(record, 'extra_data', None)
200
+ if not extra_data and hasattr(record, 'extra'):
201
+ extra_data = getattr(record, 'extra', None)
202
+ if extra_data:
203
+ context_data = extra_data.get('context_data', {})
189
204
  if context_data:
190
205
  msg += f" | Context: {context_data}"
191
- metrics = record.extra_data.get('性能指标', {})
206
+ metrics = extra_data.get('性能指标', {})
192
207
  if metrics:
193
208
  msg += f" | Metrics: {metrics}"
194
- extra = {k: v for k, v in record.extra_data.items()
209
+ extra = {k: v for k, v in extra_data.items()
195
210
  if k not in ('context_data', '性能指标')}
196
211
  if extra:
197
212
  msg += f" | Extra: {extra}"
@@ -254,6 +269,39 @@ class MyLogger:
254
269
  )
255
270
  self._queue_listener.start()
256
271
 
272
+ def _setup_multiprocess_logging(self):
273
+ """多进程安全日志:主进程写日志,子进程投递消息"""
274
+ self._mp_queue = multiprocessing.Queue(self.buffer_size)
275
+ if self._is_main_process:
276
+ # 主进程:启动写入进程
277
+ self._mp_writer_process = multiprocessing.Process(
278
+ target=self._mp_writer_worker,
279
+ args=(self._mp_queue,),
280
+ name=f"{self.name}_mp_writer",
281
+ daemon=True
282
+ )
283
+ self._mp_writer_process.start()
284
+ else:
285
+ # 子进程:不需要写入进程
286
+ pass
287
+
288
+ def _mp_writer_worker(self, log_queue):
289
+ """日志写入进程,消费队列并写日志"""
290
+ # 重新初始化logger和handlers(避免多进程fork后锁混乱)
291
+ self._init_logging()
292
+ while True:
293
+ try:
294
+ record = log_queue.get()
295
+ if record is None:
296
+ break
297
+ level, message, extra = record
298
+ self._sync_log(level, message, extra)
299
+ except Exception as e:
300
+ try:
301
+ self.logger.error(f"多进程日志写入异常: {e}", extra={'extra_data': {'mp_writer_error': str(e)}})
302
+ except:
303
+ pass
304
+
257
305
  def _get_system_metrics(self) -> Dict[str, Any]:
258
306
  """获取系统资源使用指标"""
259
307
  if not self.enable_metrics:
@@ -337,7 +385,17 @@ class MyLogger:
337
385
 
338
386
  @log_error_handler(retry_times=1, fallback_level='warning')
339
387
  def _sync_log(self, level: str, message: str, extra: Optional[Dict] = None):
340
- """同步日志记录(兼容异步,直接走logger)"""
388
+ if self.enable_multiprocess and not self._is_main_process:
389
+ # 子进程:只投递消息
390
+ try:
391
+ self._mp_queue.put((level, message, extra), block=False)
392
+ except Exception as e:
393
+ # 投递失败降级本地输出
394
+ logging.basicConfig()
395
+ fallback_logger = logging.getLogger(f"{getattr(self, 'name', 'mylogger')}_mp_fallback")
396
+ fallback_logger.warning(f"[多进程投递失败] {message} {e}")
397
+ return
398
+ # 主进程/普通模式:正常写日志
341
399
  if not hasattr(self.logger, level.lower()):
342
400
  return
343
401
  if not isinstance(message, str):
@@ -462,7 +520,7 @@ class MyLogger:
462
520
  def _format_traceback(self, exc_info):
463
521
  """格式化异常堆栈"""
464
522
  if exc_info is None:
465
- return ""
523
+ return "No traceback available"
466
524
  return ''.join(traceback.format_exception(type(exc_info), exc_info, exc_info.__traceback__))
467
525
 
468
526
  def timeit(self, message: str = "Execution time"):
@@ -481,8 +539,7 @@ class MyLogger:
481
539
 
482
540
  def __exit__(self, exc_type, exc_val, exc_tb):
483
541
  elapsed = time.time() - self.start_time
484
- self.logger.info(f"{self.message}: {elapsed:.3f}s",
485
- extra={'elapsed_seconds': elapsed})
542
+ self.logger.info(f"{self.message}: {elapsed:.3f}s", extra={'elapsed_seconds': f"{elapsed:.3f}"})
486
543
  return False
487
544
 
488
545
  def _start_flush_thread(self):
@@ -522,13 +579,27 @@ class MyLogger:
522
579
 
523
580
  def shutdown(self):
524
581
  """关闭日志记录器,确保所有日志被刷新"""
582
+ if self.enable_multiprocess and self._is_main_process and self._mp_writer_process:
583
+ try:
584
+ self._mp_queue.put(None)
585
+ self._mp_writer_process.join(timeout=5)
586
+ except:
587
+ pass
588
+ if self.enable_multiprocess and self._mp_queue is not None:
589
+ try:
590
+ self._mp_queue.close()
591
+ self._mp_queue.join_thread()
592
+ except:
593
+ pass
594
+ self._mp_queue = None
525
595
  if self.enable_async and self._queue_listener:
526
596
  self._queue_listener.stop()
527
- for handler in self.logger.handlers:
597
+ for handler in self.logger.handlers[:]:
528
598
  try:
529
599
  handler.close()
530
600
  except:
531
601
  pass
602
+ self.logger.removeHandler(handler)
532
603
  for handler in getattr(self, '_handlers', []):
533
604
  try:
534
605
  handler.close()
@@ -2,57 +2,222 @@ import traceback
2
2
  import sys
3
3
  from functools import wraps
4
4
  import inspect
5
+ import asyncio
6
+ from typing import Callable, Optional, Any, List
7
+ import logging
8
+ import json
5
9
 
6
10
 
7
- def log_on_exception(logger=None):
11
+ class _ErrorHandlerHelper:
12
+ @staticmethod
13
+ def get_default_logger():
14
+ default_logger = logging.getLogger("mdbq.error_handler.default")
15
+ handler_exists = any(isinstance(h, logging.StreamHandler) for h in default_logger.handlers)
16
+ if not handler_exists:
17
+ handler = logging.StreamHandler()
18
+ handler.setLevel(logging.INFO)
19
+ formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
20
+ handler.setFormatter(formatter)
21
+ default_logger.addHandler(handler)
22
+ default_logger.setLevel(logging.INFO)
23
+ default_logger.propagate = False
24
+ return default_logger
25
+
26
+ @staticmethod
27
+ def filter_fields(info: dict, log_fields):
28
+ if not log_fields:
29
+ return info
30
+ return {k: info[k] for k in log_fields if k in info}
31
+
32
+ @staticmethod
33
+ def build_error_info(func, e, args, kwargs, stack_summary):
34
+ tb = traceback.extract_tb(sys.exc_info()[2])
35
+ last_tb = tb[-1] if tb else None
36
+ return {
37
+ '函数': func.__name__,
38
+ '模块': func.__module__,
39
+ '类型': type(e).__name__,
40
+ '消息': str(e),
41
+ '签名': str(inspect.signature(func)),
42
+ 'args': [str(arg) for arg in args] if args else [],
43
+ 'kwargs': {k: str(v) for k, v in kwargs.items()} if kwargs else {},
44
+ '函数文件': func.__code__.co_filename,
45
+ '函数行号': func.__code__.co_firstlineno,
46
+ '异常行号': last_tb.lineno if last_tb else None,
47
+ '异常文件': last_tb.filename if last_tb else None,
48
+ '堆栈': stack_summary,
49
+ }
50
+
51
+ @staticmethod
52
+ def build_final_error_info(func, last_exception, max_retries):
53
+ tb = traceback.extract_tb(sys.exc_info()[2])
54
+ last_tb = tb[-1] if tb else None
55
+ return {
56
+ '函数': func.__name__,
57
+ '最终错误类型': type(last_exception).__name__,
58
+ '最终错误消息': str(last_exception),
59
+ '总尝试次数': max_retries,
60
+ '堆栈跟踪': traceback.format_exc(),
61
+ '异常行号': last_tb.lineno if last_tb else None,
62
+ '异常文件': last_tb.filename if last_tb else None,
63
+ }
64
+
65
+ @staticmethod
66
+ def get_stack_summary():
67
+ stack_lines = traceback.format_exc().splitlines(keepends=True)
68
+ if len(stack_lines) > 40:
69
+ return ''.join(stack_lines[:20]) + '\n...\n' + ''.join(stack_lines[-20:])
70
+ else:
71
+ return ''.join(stack_lines)
72
+
73
+
74
+ def log_on_exception(
75
+ logger=None,
76
+ *,
77
+ on_exception: Optional[Callable[[dict], None]] = None,
78
+ default_return: Any = None,
79
+ log_fields: Optional[List[str]] = None,
80
+ ):
81
+ """
82
+ :param logger: 日志对象,需实现 debug/info/warning/error/critical 方法
83
+ :param on_exception: 异常回调,参数为 error_info 字典
84
+ :param default_return: 异常时返回的默认值
85
+ :param log_fields: 只记录 error_info 的部分字段
86
+ """
87
+ if logger is not None:
88
+ for method in ("debug", "info", "warning", "error", "critical"):
89
+ if not hasattr(logger, method):
90
+ raise TypeError(
91
+ f"logger 参数必须有 {method} 方法,当前类型为: {type(logger)}"
92
+ )
8
93
  def decorator(func):
94
+ is_async = asyncio.iscoroutinefunction(func)
9
95
  @wraps(func)
10
- def wrapper(*args, **kwargs):
96
+ async def async_wrapper(*args, **kwargs):
97
+ try:
98
+ return await func(*args, **kwargs)
99
+ except Exception as e:
100
+ stack_summary = _ErrorHandlerHelper.get_stack_summary()
101
+ error_info = _ErrorHandlerHelper.build_error_info(func, e, args, kwargs, stack_summary)
102
+ error_info = _ErrorHandlerHelper.filter_fields(error_info, log_fields)
103
+ use_logger = logger if logger is not None else _ErrorHandlerHelper.get_default_logger()
104
+ if use_logger:
105
+ if logger is None:
106
+ use_logger.error(f"执行失败\n详细信息: {json.dumps(error_info, ensure_ascii=False, indent=2)}")
107
+ else:
108
+ use_logger.error("执行失败", {'details': error_info})
109
+ if on_exception:
110
+ try:
111
+ on_exception(error_info)
112
+ except Exception:
113
+ pass
114
+ return default_return
115
+ @wraps(func)
116
+ def sync_wrapper(*args, **kwargs):
11
117
  try:
12
118
  return func(*args, **kwargs)
13
119
  except Exception as e:
14
- stack_lines = traceback.format_exc().splitlines(keepends=True)
15
- if len(stack_lines) > 40:
16
- stack_summary = ''.join(stack_lines[:20]) + '\n...\n' + ''.join(stack_lines[-20:])
17
- else:
18
- stack_summary = ''.join(stack_lines)
19
- error_info = {
20
- '函数': func.__name__,
21
- '模块': func.__module__,
22
- '类型': type(e).__name__,
23
- '消息': str(e),
24
- '签名': str(inspect.signature(func)),
25
- 'args': [str(arg) for arg in args] if args else [],
26
- 'kwargs': {k: str(v) for k, v in kwargs.items()} if kwargs else {},
27
- '函数文件': func.__code__.co_filename,
28
- '函数行号': func.__code__.co_firstlineno,
29
- '异常行号': traceback.extract_tb(sys.exc_info()[2])[-1].lineno if sys.exc_info()[2] else None,
30
- '异常文件': traceback.extract_tb(sys.exc_info()[2])[-1].filename if sys.exc_info()[2] else None,
31
- '堆栈': stack_summary,
32
- }
33
- if logger:
34
- logger.error(f"执行失败", {'details': error_info})
35
-
36
- # print(error_info)
37
- # raise # 重新抛出异常
38
-
39
- return None # 或者返回其他默认值
40
-
41
- return wrapper
120
+ stack_summary = _ErrorHandlerHelper.get_stack_summary()
121
+ error_info = _ErrorHandlerHelper.build_error_info(func, e, args, kwargs, stack_summary)
122
+ error_info = _ErrorHandlerHelper.filter_fields(error_info, log_fields)
123
+ use_logger = logger if logger is not None else _ErrorHandlerHelper.get_default_logger()
124
+ if use_logger:
125
+ if logger is None:
126
+ use_logger.error(f"执行失败\n详细信息: {json.dumps(error_info, ensure_ascii=False, indent=2)}")
127
+ else:
128
+ use_logger.error("执行失败", {'details': error_info})
129
+ if on_exception:
130
+ try:
131
+ on_exception(error_info)
132
+ except Exception:
133
+ pass
134
+ return default_return
135
+ return async_wrapper if is_async else sync_wrapper
42
136
  return decorator
43
137
 
44
138
 
45
- def log_on_exception_with_retry(max_retries=3, delay=1, logger=None):
139
+ def log_on_exception_with_retry(
140
+ max_retries=3,
141
+ delay=1,
142
+ logger=None,
143
+ *,
144
+ on_exception: Optional[Callable[[dict], None]] = None,
145
+ default_return: Any = None,
146
+ log_fields: Optional[List[str]] = None,
147
+ ):
148
+ """
149
+ :param logger: 日志对象,需实现 debug/info/warning/error/critical 方法
150
+ :param on_exception: 异常回调,参数为 error_info 字典
151
+ :param default_return: 异常时返回的默认值
152
+ :param log_fields: 只记录 error_info 的部分字段
153
+ """
154
+ if logger is not None:
155
+ for method in ("debug", "info", "warning", "error", "critical"):
156
+ if not hasattr(logger, method):
157
+ raise TypeError(
158
+ f"logger 参数必须有 {method} 方法,当前类型为: {type(logger)}"
159
+ )
46
160
  def decorator(func):
161
+ is_async = asyncio.iscoroutinefunction(func)
47
162
  @wraps(func)
48
- def wrapper(*args, **kwargs):
163
+ async def async_wrapper(*args, **kwargs):
49
164
  last_exception = None
50
-
165
+ import time
166
+ for attempt in range(max_retries):
167
+ try:
168
+ return await func(*args, **kwargs)
169
+ except Exception as e:
170
+ last_exception = e
171
+ error_info = {
172
+ '函数': func.__name__,
173
+ '重试': attempt + 1,
174
+ '最大重试': max_retries,
175
+ '类型': type(e).__name__,
176
+ '消息': str(e),
177
+ }
178
+ error_info = _ErrorHandlerHelper.filter_fields(error_info, log_fields)
179
+ use_logger = logger if logger is not None else _ErrorHandlerHelper.get_default_logger()
180
+ if use_logger:
181
+ if logger is None:
182
+ use_logger.warning(f"函数 {func.__name__} 第 {attempt + 1} 次尝试失败\n详细信息: {json.dumps(error_info, ensure_ascii=False, indent=2)}")
183
+ else:
184
+ use_logger.warning(f"函数 {func.__name__} 第 {attempt + 1} 次尝试失败", {'details': error_info})
185
+ if on_exception:
186
+ try:
187
+ on_exception(error_info)
188
+ except Exception:
189
+ pass
190
+ if attempt < max_retries - 1:
191
+ await asyncio.sleep(delay)
192
+ if use_logger:
193
+ use_logger.info(f"第 {attempt + 1} 次尝试失败,{delay}秒后重试...")
194
+ else:
195
+ if use_logger:
196
+ if logger is None:
197
+ use_logger.error(f"函数 {func.__name__} 在 {max_retries} 次尝试后仍然失败\n详细信息: {json.dumps(error_info, ensure_ascii=False, indent=2)}")
198
+ else:
199
+ use_logger.error(f"函数 {func.__name__} 在 {max_retries} 次尝试后仍然失败", {'details': error_info})
200
+ final_error_info = _ErrorHandlerHelper.build_final_error_info(func, last_exception, max_retries)
201
+ final_error_info = _ErrorHandlerHelper.filter_fields(final_error_info, log_fields)
202
+ if use_logger:
203
+ if logger is None:
204
+ use_logger.error(f"最终执行失败\n详细信息: {json.dumps(final_error_info, ensure_ascii=False, indent=2)}")
205
+ else:
206
+ use_logger.error("最终执行失败", {'details': final_error_info})
207
+ if on_exception:
208
+ try:
209
+ on_exception(final_error_info)
210
+ except Exception:
211
+ pass
212
+ return default_return
213
+ @wraps(func)
214
+ def sync_wrapper(*args, **kwargs):
215
+ last_exception = None
216
+ import time
51
217
  for attempt in range(max_retries):
52
218
  try:
53
219
  return func(*args, **kwargs)
54
220
  except Exception as e:
55
- import time
56
221
  last_exception = e
57
222
  error_info = {
58
223
  '函数': func.__name__,
@@ -61,59 +226,50 @@ def log_on_exception_with_retry(max_retries=3, delay=1, logger=None):
61
226
  '类型': type(e).__name__,
62
227
  '消息': str(e),
63
228
  }
64
-
65
- if logger:
66
- logger.warning(f"函数 {func.__name__} 第 {attempt + 1} 次尝试失败", {'details': error_info})
67
-
229
+ error_info = _ErrorHandlerHelper.filter_fields(error_info, log_fields)
230
+ use_logger = logger if logger is not None else _ErrorHandlerHelper.get_default_logger()
231
+ if use_logger:
232
+ if logger is None:
233
+ use_logger.warning(f"函数 {func.__name__} 第 {attempt + 1} 次尝试失败\n详细信息: {json.dumps(error_info, ensure_ascii=False, indent=2)}")
234
+ else:
235
+ use_logger.warning(f"函数 {func.__name__} 第 {attempt + 1} 次尝试失败", {'details': error_info})
236
+ if on_exception:
237
+ try:
238
+ on_exception(error_info)
239
+ except Exception:
240
+ pass
68
241
  if attempt < max_retries - 1:
69
242
  time.sleep(delay)
70
- if logger:
71
- logger.info(f"第 {attempt + 1} 次尝试失败,{delay}秒后重试...")
243
+ if use_logger:
244
+ use_logger.info(f"第 {attempt + 1} 次尝试失败,{delay}秒后重试...")
72
245
  else:
73
- if logger:
74
- logger.error(f"❌ 函数 {func.__name__} 在 {max_retries} 次尝试后仍然失败")
75
-
76
- final_error_info = {
77
- '函数': func.__name__,
78
- '最终错误类型': type(last_exception).__name__,
79
- '最终错误消息': str(last_exception),
80
- '总尝试次数': max_retries,
81
- '堆栈跟踪': traceback.format_exc(),
82
- }
83
-
84
- if logger:
85
- logger.error(f"函数 {func.__name__} 最终执行失败", {'details': final_error_info})
86
-
87
- return None
88
-
89
- return wrapper
246
+ if use_logger:
247
+ if logger is None:
248
+ use_logger.error(f"函数 {func.__name__} 在 {max_retries} 次尝试后仍然失败\n详细信息: {json.dumps(error_info, ensure_ascii=False, indent=2)}")
249
+ else:
250
+ use_logger.error(f"函数 {func.__name__} 在 {max_retries} 次尝试后仍然失败", {'details': error_info})
251
+ final_error_info = _ErrorHandlerHelper.build_final_error_info(func, last_exception, max_retries)
252
+ final_error_info = _ErrorHandlerHelper.filter_fields(final_error_info, log_fields)
253
+ if use_logger:
254
+ if logger is None:
255
+ use_logger.error(f"最终执行失败\n详细信息: {json.dumps(final_error_info, ensure_ascii=False, indent=2)}")
256
+ else:
257
+ use_logger.error("最终执行失败", {'details': final_error_info})
258
+ if on_exception:
259
+ try:
260
+ on_exception(final_error_info)
261
+ except Exception:
262
+ pass
263
+ return default_return
264
+ return async_wrapper if is_async else sync_wrapper
90
265
  return decorator
91
266
 
92
267
 
93
268
  if __name__ == "__main__":
94
- import logging
95
- test_logger = logging.getLogger(__name__)
96
- test_logger.setLevel(logging.INFO)
97
-
98
- @log_on_exception(logger=test_logger)
269
+ @log_on_exception(logger=None)
99
270
  def divide_numbers(a, b):
100
271
  """测试函数:除法运算"""
101
272
  return a / b
102
-
103
- @log_on_exception_with_retry(max_retries=2, delay=0.5, logger=test_logger)
104
- def fetch_data(url):
105
- import random
106
- """测试函数:模拟数据获取"""
107
- if random.random() < 0.7: # 70% 概率失败
108
- raise ConnectionError("网络连接失败")
109
- return "数据获取成功"
110
-
111
- # 测试基本错误处理
112
- print("=== 测试基本错误处理 ===")
273
+
113
274
  result1 = divide_numbers(10, 0)
114
- result2 = divide_numbers(10, 2)
115
- print(f"结果: {result2}")
116
-
117
- print("\n=== 测试重试机制 ===")
118
- result3 = fetch_data("http://example.com")
119
- print(f"最终结果: {result3}")
275
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: mdbq
3
- Version: 4.0.35
3
+ Version: 4.0.37
4
4
  Home-page: https://pypi.org/project/mdbq
5
5
  Author: xigua,
6
6
  Author-email: 2587125111@qq.com
@@ -1,9 +1,9 @@
1
1
  mdbq/__init__.py,sha256=Il5Q9ATdX8yXqVxtP_nYqUhExzxPC_qk_WXQ_4h0exg,16
2
- mdbq/__version__.py,sha256=B_ZDOEojo89VxQM2P_TRRgrJ65zx2UcvpOaQkIohFPI,18
2
+ mdbq/__version__.py,sha256=1IAdkcLTP6Byg2amV6TPLMYX4EWf_WqqMKyIUbVRCfc,18
3
3
  mdbq/aggregation/__init__.py,sha256=EeDqX2Aml6SPx8363J-v1lz0EcZtgwIBYyCJV6CcEDU,40
4
4
  mdbq/aggregation/query_data.py,sha256=WtTFMN78jn43Y-nBTPAXhAK56w3wDuv_cj4YtzzGbZk,169797
5
5
  mdbq/log/__init__.py,sha256=Mpbrav0s0ifLL7lVDAuePEi1hJKiSHhxcv1byBKDl5E,15
6
- mdbq/log/mylogger.py,sha256=uC_KHsoCkxKKMVZTKYhe06Y5g6nUYuQYSEmK7YHLtlc,21754
6
+ mdbq/log/mylogger.py,sha256=iDhWkTY6I9T3IJuERWqiXKq1sNf0VuraSEq33ZxLqdw,24930
7
7
  mdbq/myconf/__init__.py,sha256=jso1oHcy6cJEfa7udS_9uO5X6kZLoPBF8l3wCYmr5dM,18
8
8
  mdbq/myconf/myconf.py,sha256=rHvQCnQRKhQ49AZBke-Z4v28hyOLmHt4MylIuB0H6yA,33516
9
9
  mdbq/mysql/__init__.py,sha256=A_DPJyAoEvTSFojiI2e94zP0FKtCkkwKP1kYUCSyQzo,11
@@ -14,7 +14,7 @@ mdbq/mysql/unique_.py,sha256=MaztT-WIyEQUs-OOYY4pFulgHVcXR1BfCy3QUz0XM_U,21127
14
14
  mdbq/mysql/uploader.py,sha256=SVlrLxoYBEpTu_I771wAehJQVFWOCqXp-lNk2JNYFOE,81881
15
15
  mdbq/other/__init__.py,sha256=jso1oHcy6cJEfa7udS_9uO5X6kZLoPBF8l3wCYmr5dM,18
16
16
  mdbq/other/download_sku_picture.py,sha256=X66sVdvVgzoNzmgVJyPtd7bjEvctEKtLPblEPF65EWc,46940
17
- mdbq/other/error_handler.py,sha256=ZeuvQM4qRYGA1a9gCijwYZ-NbA4coijMkbC2tqTNBU0,4590
17
+ mdbq/other/error_handler.py,sha256=4p5haAXSY-P78stp4Xwo_MwAngWYqyKj5ogWIuYXMeY,12631
18
18
  mdbq/other/otk.py,sha256=iclBIFbQbhlqzUbcMMoePXBpcP1eZ06ZtjnhcA_EbmE,7241
19
19
  mdbq/other/pov_city.py,sha256=AEOmCOzOwyjHi9LLZWPKi6DUuSC-_M163664I52u9qw,21050
20
20
  mdbq/other/ua_sj.py,sha256=JuVYzc_5QZ9s_oQSrTHVKkQv4S_7-CWx4oIKOARn_9U,22178
@@ -25,7 +25,7 @@ mdbq/redis/__init__.py,sha256=YtgBlVSMDphtpwYX248wGge1x-Ex_mMufz4-8W0XRmA,12
25
25
  mdbq/redis/getredis.py,sha256=vpBuNc22uj9Vr-_Dh25_wpwWM1e-072EAAIBdB_IpL0,23494
26
26
  mdbq/spider/__init__.py,sha256=RBMFXGy_jd1HXZhngB2T2XTvJqki8P_Fr-pBcwijnew,18
27
27
  mdbq/spider/aikucun.py,sha256=XptHjGzbout9IYzWAOQUpMMV5qEgLTU8pL1ZGt8oNEA,21868
28
- mdbq-4.0.35.dist-info/METADATA,sha256=okq1WCntJO--DHv5eIqIAwDggX6rHeFRfIR2xZQqnGs,364
29
- mdbq-4.0.35.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
30
- mdbq-4.0.35.dist-info/top_level.txt,sha256=2FQ-uLnCSB-OwFiWntzmwosW3X2Xqsg0ewh1axsaylA,5
31
- mdbq-4.0.35.dist-info/RECORD,,
28
+ mdbq-4.0.37.dist-info/METADATA,sha256=PUJcSDuxE3fJkdK9-9ovfVDiCmFnwpd5HNuxblXBJC0,364
29
+ mdbq-4.0.37.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
30
+ mdbq-4.0.37.dist-info/top_level.txt,sha256=2FQ-uLnCSB-OwFiWntzmwosW3X2Xqsg0ewh1axsaylA,5
31
+ mdbq-4.0.37.dist-info/RECORD,,
File without changes