mdbq 4.0.21__tar.gz → 4.0.23__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 (37) hide show
  1. {mdbq-4.0.21 → mdbq-4.0.23}/PKG-INFO +1 -1
  2. mdbq-4.0.23/mdbq/__version__.py +1 -0
  3. mdbq-4.0.23/mdbq/myconf/myconf2.py +594 -0
  4. {mdbq-4.0.21 → mdbq-4.0.23}/mdbq.egg-info/PKG-INFO +1 -1
  5. mdbq-4.0.21/mdbq/__version__.py +0 -1
  6. mdbq-4.0.21/mdbq/myconf/myconf2.py +0 -1287
  7. {mdbq-4.0.21 → mdbq-4.0.23}/README.txt +0 -0
  8. {mdbq-4.0.21 → mdbq-4.0.23}/mdbq/__init__.py +0 -0
  9. {mdbq-4.0.21 → mdbq-4.0.23}/mdbq/aggregation/__init__.py +0 -0
  10. {mdbq-4.0.21 → mdbq-4.0.23}/mdbq/aggregation/query_data.py +0 -0
  11. {mdbq-4.0.21 → mdbq-4.0.23}/mdbq/log/__init__.py +0 -0
  12. {mdbq-4.0.21 → mdbq-4.0.23}/mdbq/log/mylogger.py +0 -0
  13. {mdbq-4.0.21 → mdbq-4.0.23}/mdbq/myconf/__init__.py +0 -0
  14. {mdbq-4.0.21 → mdbq-4.0.23}/mdbq/myconf/myconf.py +0 -0
  15. {mdbq-4.0.21 → mdbq-4.0.23}/mdbq/mysql/__init__.py +0 -0
  16. {mdbq-4.0.21 → mdbq-4.0.23}/mdbq/mysql/deduplicator.py +0 -0
  17. {mdbq-4.0.21 → mdbq-4.0.23}/mdbq/mysql/mysql.py +0 -0
  18. {mdbq-4.0.21 → mdbq-4.0.23}/mdbq/mysql/s_query.py +0 -0
  19. {mdbq-4.0.21 → mdbq-4.0.23}/mdbq/mysql/unique_.py +0 -0
  20. {mdbq-4.0.21 → mdbq-4.0.23}/mdbq/mysql/uploader.py +0 -0
  21. {mdbq-4.0.21 → mdbq-4.0.23}/mdbq/other/__init__.py +0 -0
  22. {mdbq-4.0.21 → mdbq-4.0.23}/mdbq/other/download_sku_picture.py +0 -0
  23. {mdbq-4.0.21 → mdbq-4.0.23}/mdbq/other/otk.py +0 -0
  24. {mdbq-4.0.21 → mdbq-4.0.23}/mdbq/other/pov_city.py +0 -0
  25. {mdbq-4.0.21 → mdbq-4.0.23}/mdbq/other/ua_sj.py +0 -0
  26. {mdbq-4.0.21 → mdbq-4.0.23}/mdbq/pbix/__init__.py +0 -0
  27. {mdbq-4.0.21 → mdbq-4.0.23}/mdbq/pbix/pbix_refresh.py +0 -0
  28. {mdbq-4.0.21 → mdbq-4.0.23}/mdbq/pbix/refresh_all.py +0 -0
  29. {mdbq-4.0.21 → mdbq-4.0.23}/mdbq/redis/__init__.py +0 -0
  30. {mdbq-4.0.21 → mdbq-4.0.23}/mdbq/redis/getredis.py +0 -0
  31. {mdbq-4.0.21 → mdbq-4.0.23}/mdbq/spider/__init__.py +0 -0
  32. {mdbq-4.0.21 → mdbq-4.0.23}/mdbq/spider/aikucun.py +0 -0
  33. {mdbq-4.0.21 → mdbq-4.0.23}/mdbq.egg-info/SOURCES.txt +0 -0
  34. {mdbq-4.0.21 → mdbq-4.0.23}/mdbq.egg-info/dependency_links.txt +0 -0
  35. {mdbq-4.0.21 → mdbq-4.0.23}/mdbq.egg-info/top_level.txt +0 -0
  36. {mdbq-4.0.21 → mdbq-4.0.23}/setup.cfg +0 -0
  37. {mdbq-4.0.21 → mdbq-4.0.23}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: mdbq
3
- Version: 4.0.21
3
+ Version: 4.0.23
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.23'
@@ -0,0 +1,594 @@
1
+ import re
2
+ from typing import Dict, Any, Optional, Union, List, Tuple, Type, TypeVar
3
+ from pathlib import Path
4
+ from mdbq.log import mylogger
5
+ from dataclasses import dataclass, field
6
+ from enum import Enum
7
+ import time
8
+
9
+ logger = mylogger.MyLogger(
10
+ logging_mode='both',
11
+ log_level='info',
12
+ log_format='json',
13
+ max_log_size=50,
14
+ backup_count=5,
15
+ enable_async=False, # 是否启用异步日志
16
+ sample_rate=1, # 采样DEBUG/INFO日志
17
+ sensitive_fields=[], # 敏感字段过滤
18
+ enable_metrics=False, # 是否启用性能指标
19
+ )
20
+
21
+ T = TypeVar('T') # 类型变量
22
+
23
+
24
+ class ConfigError(Exception):
25
+ """配置相关的基础异常类"""
26
+ def __init__(self, message: str, file_path: Optional[Union[str, Path]] = None,
27
+ section: Optional[str] = None, key: Optional[str] = None):
28
+ self.message = message
29
+ self.file_path = str(file_path) if file_path else None
30
+ self.section = section
31
+ self.key = key
32
+ super().__init__(self._format_message())
33
+
34
+ def _format_message(self) -> str:
35
+ """格式化错误消息"""
36
+ parts = [self.message]
37
+ if self.file_path:
38
+ parts.append(f"文件: {self.file_path}")
39
+ if self.section:
40
+ parts.append(f"节: [{self.section}]")
41
+ if self.key:
42
+ parts.append(f"键: {self.key}")
43
+ return " | ".join(parts)
44
+
45
+
46
+ class ConfigFileNotFoundError(ConfigError):
47
+ """配置文件不存在异常"""
48
+ def __init__(self, file_path: Union[str, Path]):
49
+ super().__init__("配置文件不存在", file_path=file_path)
50
+
51
+
52
+ class ConfigReadError(ConfigError):
53
+ """读取配置文件失败异常"""
54
+ def __init__(self, file_path: Union[str, Path], original_error: Exception):
55
+ super().__init__(
56
+ f"读取配置文件失败: {str(original_error)}",
57
+ file_path=file_path
58
+ )
59
+ self.original_error = original_error
60
+
61
+
62
+ class ConfigWriteError(ConfigError):
63
+ """写入配置文件失败异常"""
64
+ def __init__(self, file_path: Union[str, Path], original_error: Exception):
65
+ super().__init__(
66
+ f"写入配置文件失败: {str(original_error)}",
67
+ file_path=file_path
68
+ )
69
+ self.original_error = original_error
70
+
71
+
72
+ class ConfigValueError(ConfigError):
73
+ """配置值无效异常"""
74
+ def __init__(self, message: str, file_path: Union[str, Path],
75
+ section: Optional[str] = None, key: Optional[str] = None):
76
+ super().__init__(message, file_path=file_path, section=section, key=key)
77
+
78
+
79
+ class ConfigSectionNotFoundError(ConfigError):
80
+ """配置节不存在异常"""
81
+ def __init__(self, file_path: Union[str, Path], section: str):
82
+ super().__init__(
83
+ f"配置节不存在",
84
+ file_path=file_path,
85
+ section=section
86
+ )
87
+
88
+
89
+ class ConfigKeyNotFoundError(ConfigError):
90
+ """配置键不存在异常"""
91
+ def __init__(self, file_path: Union[str, Path], section: str, key: str):
92
+ super().__init__(
93
+ f"配置键不存在",
94
+ file_path=file_path,
95
+ section=section,
96
+ key=key
97
+ )
98
+
99
+
100
+ class CommentStyle(Enum):
101
+ """配置文件支持的注释风格"""
102
+ HASH = '#' # Python风格注释
103
+ DOUBLE_SLASH = '//' # C风格注释
104
+ SEMICOLON = ';' # INI风格注释
105
+
106
+
107
+ @dataclass
108
+ class ConfigOptions:
109
+ """配置解析器选项"""
110
+ comment_styles: List[CommentStyle] = field(default_factory=lambda: [CommentStyle.HASH, CommentStyle.DOUBLE_SLASH])
111
+ encoding: str = 'utf-8'
112
+ auto_create: bool = False
113
+ strip_values: bool = True
114
+ preserve_comments: bool = True
115
+ default_section: str = 'DEFAULT'
116
+ separators: List[str] = field(default_factory=lambda: ['=', ':', ':'])
117
+ cache_ttl: int = 300 # 5分钟缓存过期
118
+ validate_keys: bool = True
119
+ key_pattern: str = r'^[a-zA-Z0-9_\-\.]+$'
120
+ case_sensitive: bool = False
121
+
122
+
123
+ class ConfigParser:
124
+ """配置文件解析器"""
125
+
126
+ def __init__(self, options: Optional[ConfigOptions] = None):
127
+ self.options = options or ConfigOptions()
128
+ self._config_cache: Dict[str, Dict[str, Any]] = {}
129
+ self._cache_timestamps: Dict[str, float] = {}
130
+ self._comments_cache: Dict[str, Dict[str, List[str]]] = {}
131
+ self._section_map: Dict[str, Dict[str, str]] = {}
132
+ self._current_file: Optional[Path] = None
133
+
134
+ def __enter__(self) -> 'ConfigParser':
135
+ return self
136
+
137
+ def __exit__(self, exc_type: Optional[Type[BaseException]],
138
+ exc_val: Optional[BaseException],
139
+ exc_tb: Optional[Any]) -> None:
140
+ self._current_file = None
141
+
142
+ def open(self, file_path: Union[str, Path]) -> 'ConfigParser':
143
+ """打开配置文件"""
144
+ file_path = Path(file_path)
145
+ if not file_path.exists() and not self.options.auto_create:
146
+ raise ConfigFileNotFoundError(file_path)
147
+ self._current_file = file_path
148
+ return self
149
+
150
+ def _ensure_file_open(self) -> None:
151
+ """确保文件已打开"""
152
+ if self._current_file is None:
153
+ raise ConfigError("未打开任何配置文件,请先调用 open() 方法")
154
+
155
+ def _is_comment_line(self, line: str) -> bool:
156
+ """判断是否为注释行"""
157
+ stripped = line.strip()
158
+ return any(stripped.startswith(style.value) for style in self.options.comment_styles)
159
+
160
+ def _extract_comment(self, line: str) -> Tuple[str, str]:
161
+ """从行中提取注释"""
162
+ for style in self.options.comment_styles:
163
+ comment_match = re.search(fr'\s+{re.escape(style.value)}.*$', line)
164
+ if comment_match:
165
+ return line[:comment_match.start()].strip(), comment_match.group(0)
166
+ return line.strip(), ''
167
+
168
+ def _split_key_value(self, line: str) -> Optional[Tuple[str, str]]:
169
+ """分割配置行为键值对"""
170
+ for sep in self.options.separators:
171
+ if sep in line:
172
+ key_part, value_part = line.split(sep, 1)
173
+ return key_part.strip(), value_part
174
+
175
+ for sep in [':', ':']:
176
+ if sep in line:
177
+ pattern = fr'\s*{re.escape(sep)}\s*'
178
+ parts = re.split(pattern, line, 1)
179
+ if len(parts) == 2:
180
+ return parts[0].strip(), parts[1]
181
+
182
+ return None
183
+
184
+ def _validate_key(self, key: str) -> bool:
185
+ """验证键名是否合法"""
186
+ if not self.options.validate_keys:
187
+ return True
188
+ return bool(re.match(self.options.key_pattern, key))
189
+
190
+ def _get_cached_config(self, file_path: str) -> Optional[Dict[str, Any]]:
191
+ """获取缓存的配置"""
192
+ if file_path not in self._config_cache or file_path not in self._cache_timestamps:
193
+ return None
194
+
195
+ if time.time() - self._cache_timestamps[file_path] > self.options.cache_ttl:
196
+ return None
197
+
198
+ return self._config_cache[file_path]
199
+
200
+ def _update_cache(self, file_path: str, config: Dict[str, Any]) -> None:
201
+ """更新配置缓存"""
202
+ self._config_cache[file_path] = config
203
+ self._cache_timestamps[file_path] = time.time()
204
+
205
+ def _normalize_section(self, section: str) -> str:
206
+ """标准化节名称"""
207
+ return section if self.options.case_sensitive else section.lower()
208
+
209
+ def _get_original_section(self, file_path: str, normalized_section: str) -> Optional[str]:
210
+ """获取原始节名称"""
211
+ if self.options.case_sensitive:
212
+ return normalized_section
213
+ return self._section_map.get(file_path, {}).get(normalized_section)
214
+
215
+ def _update_section_map(self, file_path: str, section: str) -> None:
216
+ """更新节名称映射"""
217
+ if not self.options.case_sensitive:
218
+ normalized = self._normalize_section(section)
219
+ if file_path not in self._section_map:
220
+ self._section_map[file_path] = {}
221
+ self._section_map[file_path][normalized] = section
222
+
223
+ def _clear_cache(self, file_path: Optional[str] = None) -> None:
224
+ """清除配置缓存"""
225
+ if file_path:
226
+ self._config_cache.pop(file_path, None)
227
+ self._cache_timestamps.pop(file_path, None)
228
+ self._comments_cache.pop(file_path, None)
229
+ self._section_map.pop(file_path, None)
230
+ else:
231
+ self._config_cache.clear()
232
+ self._cache_timestamps.clear()
233
+ self._comments_cache.clear()
234
+ self._section_map.clear()
235
+
236
+ def _convert_value(self, value: str, target_type: Type[T], file_path: Optional[Union[str, Path]] = None, key: Optional[str] = None) -> T:
237
+ """转换配置值到指定类型"""
238
+ try:
239
+ if target_type == bool:
240
+ return bool(value.lower() in ('true', 'yes', '1', 'on'))
241
+ elif target_type == list:
242
+ if not value.strip():
243
+ return []
244
+ for sep in [',', ';', '|', ' ']:
245
+ if sep in value:
246
+ return [item.strip() for item in value.split(sep) if item.strip()]
247
+ return [value.strip()]
248
+ elif target_type == tuple:
249
+ if not value.strip():
250
+ return ()
251
+ for sep in [',', ';', '|', ' ']:
252
+ if sep in value:
253
+ return tuple(item.strip() for item in value.split(sep) if item.strip())
254
+ return (value.strip(),)
255
+ elif target_type == set or target_type == frozenset:
256
+ if not value.strip():
257
+ return set() if target_type == set else frozenset()
258
+ for sep in [',', ';', '|', ' ']:
259
+ if sep in value:
260
+ items = [item.strip() for item in value.split(sep) if item.strip()]
261
+ return set(items) if target_type == set else frozenset(items)
262
+ return set([value.strip()]) if target_type == set else frozenset([value.strip()])
263
+ elif target_type == dict:
264
+ if not value.strip():
265
+ return {}
266
+ result = {}
267
+ for sep in [',', ';', '|']:
268
+ if sep in value:
269
+ pairs = [pair.strip() for pair in value.split(sep) if pair.strip()]
270
+ for pair in pairs:
271
+ if '=' in pair:
272
+ key_, val = pair.split('=', 1)
273
+ result[key_.strip()] = val.strip()
274
+ return result
275
+ if '=' in value:
276
+ key_, val = value.split('=', 1)
277
+ return {key_.strip(): val.strip()}
278
+ return {}
279
+ elif target_type == int:
280
+ value = value.strip().lower()
281
+ if value.startswith('0x'):
282
+ return int(value, 16)
283
+ elif value.startswith('0o'):
284
+ return int(value, 8)
285
+ elif value.startswith('0b'):
286
+ return int(value, 2)
287
+ return int(value)
288
+ elif target_type == float:
289
+ return float(value)
290
+ elif target_type == complex:
291
+ return complex(value)
292
+ elif target_type == bytes:
293
+ return value.encode('utf-8')
294
+ elif target_type == bytearray:
295
+ return bytearray(value.encode('utf-8'))
296
+ elif target_type == range:
297
+ parts = value.split(':')
298
+ if len(parts) == 2:
299
+ return range(int(parts[0]), int(parts[1]))
300
+ elif len(parts) == 3:
301
+ return range(int(parts[0]), int(parts[1]), int(parts[2]))
302
+ raise ValueError("Invalid range format")
303
+ return target_type(value)
304
+ except (ValueError, TypeError) as e:
305
+ raise ConfigValueError(
306
+ f"无法将值 '{value}' 转换为类型 {target_type.__name__}",
307
+ file_path=file_path,
308
+ key=key
309
+ )
310
+
311
+ def get_value(self, file_path: Optional[Union[str, Path]] = None, key: str = None,
312
+ section: Optional[str] = None, default: Any = None,
313
+ value_type: Optional[Type[T]] = None) -> T:
314
+ """获取指定配置项的值"""
315
+ if file_path is None:
316
+ self._ensure_file_open()
317
+ file_path = self._current_file
318
+ if not self._validate_key(key):
319
+ raise ConfigValueError(f"无效的键名: {key}", file_path=file_path, key=key)
320
+ config = self.read(file_path)
321
+ section = section or self.options.default_section
322
+ normalized_section = self._normalize_section(section)
323
+ original_section = self._get_original_section(str(file_path), normalized_section)
324
+ if original_section is None:
325
+ if default is not None:
326
+ return default
327
+ raise ConfigSectionNotFoundError(file_path, section)
328
+ if key not in config[original_section]:
329
+ if default is not None:
330
+ return default
331
+ raise ConfigKeyNotFoundError(file_path, original_section, key)
332
+ value = config[original_section][key]
333
+ if value_type is not None:
334
+ return self._convert_value(value, value_type, file_path=file_path, key=key)
335
+ return value
336
+
337
+ def get_values(self, keys: List[Tuple[str, str]],
338
+ file_path: Optional[Union[str, Path]] = None,
339
+ defaults: Optional[Dict[str, Any]] = None,
340
+ value_types: Optional[Dict[str, Type]] = None) -> Dict[str, Any]:
341
+ """批量获取多个配置项的值"""
342
+ if file_path is None:
343
+ self._ensure_file_open()
344
+ file_path = self._current_file
345
+ defaults = defaults or {}
346
+ value_types = value_types or {}
347
+ result = {}
348
+
349
+ for section, key in keys:
350
+ try:
351
+ value = self.get_value(
352
+ file_path=file_path,
353
+ key=key,
354
+ section=section,
355
+ default=defaults.get(key),
356
+ value_type=value_types.get(key)
357
+ )
358
+ result[key] = value
359
+ except (ConfigSectionNotFoundError, ConfigKeyNotFoundError) as e:
360
+ if key in defaults:
361
+ result[key] = defaults[key]
362
+ else:
363
+ raise e
364
+
365
+ return result
366
+
367
+ def get_section_values(self, keys: List[str],
368
+ section: Optional[str] = None,
369
+ file_path: Optional[Union[str, Path]] = None,
370
+ defaults: Optional[Dict[str, Any]] = None,
371
+ value_types: Optional[Dict[str, Type]] = None) -> Tuple[Any, ...]:
372
+ """获取指定节点下多个键的值元组"""
373
+ if file_path is None:
374
+ self._ensure_file_open()
375
+ file_path = self._current_file
376
+ defaults = defaults or {}
377
+ value_types = value_types or {}
378
+ result = []
379
+
380
+ for key in keys:
381
+ try:
382
+ value = self.get_value(
383
+ file_path=file_path,
384
+ key=key,
385
+ section=section,
386
+ default=defaults.get(key),
387
+ value_type=value_types.get(key)
388
+ )
389
+ result.append(value)
390
+ except (ConfigSectionNotFoundError, ConfigKeyNotFoundError) as e:
391
+ if key in defaults:
392
+ result.append(defaults[key])
393
+ else:
394
+ raise e
395
+
396
+ return tuple(result)
397
+
398
+ def set_value(self, key: str, value: Any,
399
+ section: Optional[str] = None,
400
+ file_path: Optional[Union[str, Path]] = None,
401
+ value_type: Optional[Type] = None) -> None:
402
+ """设置指定配置项的值"""
403
+ if file_path is None:
404
+ self._ensure_file_open()
405
+ file_path = self._current_file
406
+ if not self._validate_key(key):
407
+ raise ConfigValueError(f"无效的键名: {key}", file_path=file_path, key=key)
408
+ section = section or self.options.default_section
409
+ original_lines = []
410
+ if file_path.exists():
411
+ with open(file_path, 'r', encoding=self.options.encoding) as file:
412
+ original_lines = file.readlines()
413
+ config = self.read(file_path)
414
+ if section not in config:
415
+ config[section] = {}
416
+ if value_type is not None:
417
+ try:
418
+ if value_type == bool:
419
+ if isinstance(value, str):
420
+ value = value.lower() in ('true', 'yes', '1', 'on')
421
+ else:
422
+ value = bool(value)
423
+ else:
424
+ value = value_type(value)
425
+ except (ValueError, TypeError) as e:
426
+ raise ConfigValueError(
427
+ f"无法将值 '{value}' 转换为类型 {value_type.__name__}",
428
+ file_path=file_path,
429
+ section=section,
430
+ key=key
431
+ )
432
+ if isinstance(value, bool):
433
+ value = str(value).lower()
434
+ else:
435
+ value = str(value)
436
+ config[section][key] = value
437
+ try:
438
+ file_path.parent.mkdir(parents=True, exist_ok=True)
439
+ with open(file_path, 'w', encoding=self.options.encoding) as file:
440
+ current_section = self.options.default_section
441
+ section_separators = {}
442
+ for line in original_lines:
443
+ stripped_line = line.strip()
444
+ if not stripped_line:
445
+ file.write(line)
446
+ continue
447
+ if stripped_line.startswith('[') and stripped_line.endswith(']'):
448
+ current_section = stripped_line[1:-1]
449
+ file.write(line)
450
+ continue
451
+ if self._is_comment_line(stripped_line):
452
+ file.write(line)
453
+ continue
454
+ key_value = self._split_key_value(stripped_line)
455
+ if key_value:
456
+ orig_key, orig_value = key_value
457
+ for sep in self.options.separators:
458
+ if sep in line:
459
+ section_separators.setdefault(current_section, {})[orig_key] = sep
460
+ break
461
+ if current_section == section and orig_key == key:
462
+ separator = section_separators.get(current_section, {}).get(orig_key, self.options.separators[0])
463
+ comment = ''
464
+ for style in self.options.comment_styles:
465
+ comment_match = re.search(fr'\s+{re.escape(style.value)}.*$', line)
466
+ if comment_match:
467
+ comment = comment_match.group(0)
468
+ break
469
+ file.write(f'{key}{separator}{value}{comment}\n')
470
+ else:
471
+ file.write(line)
472
+ else:
473
+ file.write(line)
474
+ if section not in [line.strip()[1:-1] for line in original_lines if line.strip().startswith('[') and line.strip().endswith(']')]:
475
+ file.write(f'\n[{section}]\n')
476
+ file.write(f'{key}={value}\n')
477
+ self._clear_cache(str(file_path))
478
+ except Exception as e:
479
+ raise ConfigWriteError(file_path, e)
480
+
481
+ def read(self, file_path: Optional[Union[str, Path]] = None) -> Dict[str, Any]:
482
+ """读取配置文件内容"""
483
+ if file_path is None:
484
+ self._ensure_file_open()
485
+ file_path = self._current_file
486
+ else:
487
+ file_path = Path(file_path)
488
+
489
+ cached_config = self._get_cached_config(str(file_path))
490
+ if cached_config is not None:
491
+ return cached_config
492
+
493
+ if not file_path.exists():
494
+ if not self.options.auto_create:
495
+ raise ConfigFileNotFoundError(file_path)
496
+ logger.info(f'配置文件不存在,将创建: {file_path}')
497
+ file_path.parent.mkdir(parents=True, exist_ok=True)
498
+ file_path.touch()
499
+ return {}
500
+
501
+ try:
502
+ with open(file_path, 'r', encoding=self.options.encoding) as file:
503
+ config = {}
504
+ current_section = self.options.default_section
505
+ section_comments = []
506
+
507
+ for line in file:
508
+ stripped_line = line.strip()
509
+
510
+ if not stripped_line or self._is_comment_line(stripped_line):
511
+ if self.options.preserve_comments:
512
+ section_comments.append(line.rstrip())
513
+ continue
514
+
515
+ if stripped_line.startswith('[') and stripped_line.endswith(']'):
516
+ current_section = stripped_line[1:-1]
517
+ if not self._validate_key(current_section):
518
+ raise ConfigValueError(
519
+ f"无效的节名: {current_section}",
520
+ file_path=file_path,
521
+ section=current_section
522
+ )
523
+ self._update_section_map(str(file_path), current_section)
524
+ if current_section not in config:
525
+ config[current_section] = {}
526
+ if self.options.preserve_comments:
527
+ self._comments_cache.setdefault(str(file_path), {}).setdefault(current_section, []).extend(section_comments)
528
+ section_comments = []
529
+ continue
530
+
531
+ key_value = self._split_key_value(stripped_line)
532
+ if key_value:
533
+ key, value = key_value
534
+ if not self._validate_key(key):
535
+ raise ConfigValueError(
536
+ f"无效的键名: {key}",
537
+ file_path=file_path,
538
+ section=current_section,
539
+ key=key
540
+ )
541
+ value, comment = self._extract_comment(value)
542
+
543
+ if self.options.strip_values:
544
+ value = value.strip()
545
+
546
+ if current_section not in config:
547
+ config[current_section] = {}
548
+
549
+ config[current_section][key] = value
550
+ if self.options.preserve_comments and comment:
551
+ self._comments_cache.setdefault(str(file_path), {}).setdefault(current_section, []).append(comment)
552
+
553
+ self._update_cache(str(file_path), config)
554
+ return config
555
+
556
+ except Exception as e:
557
+ raise ConfigReadError(file_path, e)
558
+
559
+
560
+ def main() -> None:
561
+ """示例用法"""
562
+ config_file = Path('/Users/xigua/spd.txt')
563
+ try:
564
+ # 方式1:使用上下文管理器
565
+ with ConfigParser() as parser:
566
+ parser.open(config_file)
567
+ host, port, username, password = parser.get_section_values(
568
+ keys=['host', 'port', 'username', 'password'],
569
+ section='mysql'
570
+ )
571
+ print("方式1结果:", host, port, username, password)
572
+ parser.set_value('username', 'root', section='mysql')
573
+ parser.set_value('port', 3306, section='mysql')
574
+ # 方式2:链式调用
575
+ parser = ConfigParser()
576
+ host, port, username, password = parser.open(config_file).get_section_values(
577
+ keys=['host', 'port', 'username', 'password'],
578
+ section='mysql'
579
+ )
580
+ print("\n方式2结果:", host, port, username, password)
581
+ # 方式3:传统方式
582
+ parser = ConfigParser()
583
+ host, port, username, password = parser.get_section_values(
584
+ file_path=config_file,
585
+ section='mysql',
586
+ keys=['host', 'port', 'username', 'password']
587
+ )
588
+ print("\n方式3结果:", host, port, username, password)
589
+ except Exception as e:
590
+ print(f"配置文件操作异常: {e}")
591
+
592
+
593
+ if __name__ == '__main__':
594
+ main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: mdbq
3
- Version: 4.0.21
3
+ Version: 4.0.23
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.21'