mdbq 4.0.23__py3-none-any.whl → 4.0.25__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/myconf/myconf.py CHANGED
@@ -21,21 +21,16 @@ logger = mylogger.MyLogger(
21
21
  T = TypeVar('T') # 类型变量
22
22
 
23
23
 
24
- class ConfigError(Exception):
25
- """配置相关的基础异常类
26
-
27
- Attributes:
28
- message: 错误消息
29
- file_path: 配置文件路径
30
- section: 配置节名称
31
- key: 配置键名称
32
- """
24
+ class ConfigException(Exception):
25
+ """配置异常基类"""
33
26
  def __init__(self, message: str, file_path: Optional[Union[str, Path]] = None,
34
- section: Optional[str] = None, key: Optional[str] = None):
27
+ section: Optional[str] = None, key: Optional[str] = None,
28
+ original_error: Optional[Exception] = None):
35
29
  self.message = message
36
30
  self.file_path = str(file_path) if file_path else None
37
31
  self.section = section
38
32
  self.key = key
33
+ self.original_error = original_error
39
34
  super().__init__(self._format_message())
40
35
 
41
36
  def _format_message(self) -> str:
@@ -47,70 +42,69 @@ class ConfigError(Exception):
47
42
  parts.append(f"节: [{self.section}]")
48
43
  if self.key:
49
44
  parts.append(f"键: {self.key}")
45
+ if self.original_error:
46
+ parts.append(f"原始错误: {str(self.original_error)}")
50
47
  return " | ".join(parts)
51
48
 
49
+ @classmethod
50
+ def file_not_found(cls, file_path: Union[str, Path]) -> 'ConfigException':
51
+ """配置文件不存在异常"""
52
+ return cls("配置文件不存在", file_path=file_path)
52
53
 
53
- class ConfigFileNotFoundError(ConfigError):
54
- """当指定的配置文件不存在时抛出的异常"""
55
- def __init__(self, file_path: Union[str, Path]):
56
- super().__init__("配置文件不存在", file_path=file_path)
54
+ @classmethod
55
+ def read_error(cls, file_path: Union[str, Path], error: Exception) -> 'ConfigException':
56
+ """读取配置文件失败异常"""
57
+ return cls("读取配置文件失败", file_path=file_path, original_error=error)
57
58
 
59
+ @classmethod
60
+ def write_error(cls, file_path: Union[str, Path], error: Exception) -> 'ConfigException':
61
+ """写入配置文件失败异常"""
62
+ return cls("写入配置文件失败", file_path=file_path, original_error=error)
58
63
 
59
- class ConfigReadError(ConfigError):
60
- """当读取配置文件失败时抛出的异常
61
-
62
- Attributes:
63
- original_error: 原始错误对象
64
- """
65
- def __init__(self, file_path: Union[str, Path], original_error: Exception):
66
- super().__init__(
67
- f"读取配置文件失败: {str(original_error)}",
68
- file_path=file_path
69
- )
70
- self.original_error = original_error
64
+ @classmethod
65
+ def value_error(cls, message: str, file_path: Union[str, Path],
66
+ section: Optional[str] = None, key: Optional[str] = None) -> 'ConfigException':
67
+ """配置值无效异常"""
68
+ return cls(message, file_path=file_path, section=section, key=key)
71
69
 
70
+ @classmethod
71
+ def section_not_found(cls, file_path: Union[str, Path], section: str) -> 'ConfigException':
72
+ """配置节不存在异常"""
73
+ return cls("配置节不存在", file_path=file_path, section=section)
72
74
 
73
- class ConfigWriteError(ConfigError):
74
- """当写入配置文件失败时抛出的异常
75
-
76
- Attributes:
77
- original_error: 原始错误对象
78
- """
79
- def __init__(self, file_path: Union[str, Path], original_error: Exception):
80
- super().__init__(
81
- f"写入配置文件失败: {str(original_error)}",
82
- file_path=file_path
83
- )
84
- self.original_error = original_error
85
-
86
-
87
- class ConfigValueError(ConfigError):
88
- """当配置值无效时抛出的异常"""
89
- def __init__(self, message: str, file_path: Union[str, Path],
90
- section: Optional[str] = None, key: Optional[str] = None):
91
- super().__init__(message, file_path=file_path, section=section, key=key)
75
+ @classmethod
76
+ def key_not_found(cls, file_path: Union[str, Path], section: str, key: str) -> 'ConfigException':
77
+ """配置键不存在异常"""
78
+ return cls("配置键不存在", file_path=file_path, section=section, key=key)
92
79
 
80
+ @classmethod
81
+ def validation_error(cls, message: str, file_path: Union[str, Path],
82
+ section: Optional[str] = None, key: Optional[str] = None) -> 'ConfigException':
83
+ """配置验证失败异常"""
84
+ return cls(f"配置验证失败: {message}", file_path=file_path, section=section, key=key)
93
85
 
94
- class ConfigSectionNotFoundError(ConfigError):
95
- """当指定的配置节不存在时抛出的异常"""
96
- def __init__(self, file_path: Union[str, Path], section: str):
97
- super().__init__(
98
- f"配置节不存在",
99
- file_path=file_path,
100
- section=section
101
- )
102
-
103
-
104
- class ConfigKeyNotFoundError(ConfigError):
105
- """当指定的配置键不存在时抛出的异常"""
106
- def __init__(self, file_path: Union[str, Path], section: str, key: str):
107
- super().__init__(
108
- f"配置键不存在",
86
+ @classmethod
87
+ def conversion_error(cls, value: Any, target_type: Type, file_path: Union[str, Path],
88
+ section: Optional[str] = None, key: Optional[str] = None) -> 'ConfigException':
89
+ """类型转换失败异常"""
90
+ return cls(
91
+ f"无法将值 '{value}' 转换为类型 {target_type.__name__}",
109
92
  file_path=file_path,
110
93
  section=section,
111
94
  key=key
112
95
  )
113
96
 
97
+ @classmethod
98
+ def invalid_key_error(cls, key: str, file_path: Union[str, Path],
99
+ section: Optional[str] = None) -> 'ConfigException':
100
+ """无效的键名异常"""
101
+ return cls(f"无效的键名: {key}", file_path=file_path, section=section, key=key)
102
+
103
+ @classmethod
104
+ def invalid_section_error(cls, section: str, file_path: Union[str, Path]) -> 'ConfigException':
105
+ """无效的节名异常"""
106
+ return cls(f"无效的节名: {section}", file_path=file_path, section=section)
107
+
114
108
 
115
109
  class CommentStyle(Enum):
116
110
  """配置文件支持的注释风格"""
@@ -121,21 +115,7 @@ class CommentStyle(Enum):
121
115
 
122
116
  @dataclass
123
117
  class ConfigOptions:
124
- """配置解析器的选项类
125
-
126
- Attributes:
127
- comment_styles: 支持的注释风格列表
128
- encoding: 文件编码
129
- auto_create: 是否自动创建不存在的配置文件
130
- strip_values: 是否去除配置值的首尾空白
131
- preserve_comments: 是否保留注释
132
- default_section: 默认配置节名称
133
- separators: 支持的分隔符列表
134
- cache_ttl: 缓存过期时间(秒)
135
- validate_keys: 是否验证键名
136
- key_pattern: 键名正则表达式模式
137
- case_sensitive: 是否区分大小写
138
- """
118
+ """配置解析器选项"""
139
119
  comment_styles: List[CommentStyle] = field(default_factory=lambda: [CommentStyle.HASH, CommentStyle.DOUBLE_SLASH])
140
120
  encoding: str = 'utf-8'
141
121
  auto_create: bool = False
@@ -150,83 +130,44 @@ class ConfigOptions:
150
130
 
151
131
 
152
132
  class ConfigParser:
153
- """配置文件解析器,用于读取和写入配置文件
154
-
155
- Attributes:
156
- options: 解析器配置选项
157
- _config_cache: 配置缓存,用于存储已读取的配置
158
- _cache_timestamps: 缓存时间戳,用于管理缓存过期
159
- _comments_cache: 注释缓存,用于存储每个配置节的注释
160
- _section_map: 用于存储大小写映射
161
- _current_file: 当前正在处理的文件路径
162
- """
133
+ """配置文件解析器"""
163
134
 
164
135
  def __init__(self, options: Optional[ConfigOptions] = None):
165
136
  self.options = options or ConfigOptions()
166
137
  self._config_cache: Dict[str, Dict[str, Any]] = {}
167
138
  self._cache_timestamps: Dict[str, float] = {}
168
139
  self._comments_cache: Dict[str, Dict[str, List[str]]] = {}
169
- self._section_map: Dict[str, Dict[str, str]] = {} # 用于存储大小写映射
170
- self._current_file: Optional[Path] = None # 当前正在处理的文件路径
140
+ self._section_map: Dict[str, Dict[str, str]] = {}
141
+ self._current_file: Optional[Path] = None
171
142
 
172
143
  def __enter__(self) -> 'ConfigParser':
173
- """进入上下文管理器
174
-
175
- Returns:
176
- ConfigParser: 返回当前实例
177
- """
178
144
  return self
179
145
 
180
146
  def __exit__(self, exc_type: Optional[Type[BaseException]],
181
147
  exc_val: Optional[BaseException],
182
148
  exc_tb: Optional[Any]) -> None:
183
- """退出上下文管理器
184
-
185
- Args:
186
- exc_type: 异常类型
187
- exc_val: 异常值
188
- exc_tb: 异常追踪信息
189
- """
190
149
  self._current_file = None
191
150
 
192
151
  def open(self, file_path: Union[str, Path]) -> 'ConfigParser':
193
- """打开配置文件
194
-
195
- Args:
196
- file_path: 配置文件路径
197
-
198
- Returns:
199
- ConfigParser: 返回当前实例,支持链式调用
200
-
201
- Raises:
202
- ConfigFileNotFoundError: 当配置文件不存在且未启用自动创建时
203
- """
152
+ """打开配置文件"""
204
153
  file_path = Path(file_path)
205
154
  if not file_path.exists() and not self.options.auto_create:
206
- raise ConfigFileNotFoundError(file_path)
155
+ raise ConfigException.file_not_found(file_path)
207
156
  self._current_file = file_path
208
157
  return self
209
158
 
210
159
  def _ensure_file_open(self) -> None:
211
- """确保文件已打开
212
-
213
- Raises:
214
- ConfigError: 当文件未打开时
215
- """
160
+ """确保文件已打开"""
216
161
  if self._current_file is None:
217
- raise ConfigError("未打开任何配置文件,请先调用 open() 方法")
162
+ raise ConfigException("未打开任何配置文件,请先调用 open() 方法")
218
163
 
219
164
  def _is_comment_line(self, line: str) -> bool:
220
- """判断一行是否为注释行"""
165
+ """判断是否为注释行"""
221
166
  stripped = line.strip()
222
167
  return any(stripped.startswith(style.value) for style in self.options.comment_styles)
223
168
 
224
169
  def _extract_comment(self, line: str) -> Tuple[str, str]:
225
- """从行中提取注释
226
-
227
- Returns:
228
- Tuple[str, str]: (去除注释后的行内容, 注释内容)
229
- """
170
+ """从行中提取注释"""
230
171
  for style in self.options.comment_styles:
231
172
  comment_match = re.search(fr'\s+{re.escape(style.value)}.*$', line)
232
173
  if comment_match:
@@ -234,14 +175,7 @@ class ConfigParser:
234
175
  return line.strip(), ''
235
176
 
236
177
  def _split_key_value(self, line: str) -> Optional[Tuple[str, str]]:
237
- """分割配置行为键值对
238
-
239
- Args:
240
- line: 要分割的配置行
241
-
242
- Returns:
243
- Optional[Tuple[str, str]]: 键值对元组,如果无法分割则返回None
244
- """
178
+ """分割配置行为键值对"""
245
179
  for sep in self.options.separators:
246
180
  if sep in line:
247
181
  key_part, value_part = line.split(sep, 1)
@@ -263,11 +197,8 @@ class ConfigParser:
263
197
  return bool(re.match(self.options.key_pattern, key))
264
198
 
265
199
  def _get_cached_config(self, file_path: str) -> Optional[Dict[str, Any]]:
266
- """获取缓存的配置,如果过期则返回None"""
267
- if file_path not in self._config_cache:
268
- return None
269
-
270
- if file_path not in self._cache_timestamps:
200
+ """获取缓存的配置"""
201
+ if file_path not in self._config_cache or file_path not in self._cache_timestamps:
271
202
  return None
272
203
 
273
204
  if time.time() - self._cache_timestamps[file_path] > self.options.cache_ttl:
@@ -281,10 +212,8 @@ class ConfigParser:
281
212
  self._cache_timestamps[file_path] = time.time()
282
213
 
283
214
  def _normalize_section(self, section: str) -> str:
284
- """标准化节名称(处理大小写)"""
285
- if self.options.case_sensitive:
286
- return section
287
- return section.lower()
215
+ """标准化节名称"""
216
+ return section.replace(' ', '').lower()
288
217
 
289
218
  def _get_original_section(self, file_path: str, normalized_section: str) -> Optional[str]:
290
219
  """获取原始节名称"""
@@ -313,73 +242,50 @@ class ConfigParser:
313
242
  self._comments_cache.clear()
314
243
  self._section_map.clear()
315
244
 
316
- def _convert_value(self, value: str, target_type: Type[T]) -> T:
317
- """转换配置值到指定类型
318
-
319
- Args:
320
- value: 要转换的值
321
- target_type: 目标类型
322
-
323
- Returns:
324
- T: 转换后的值
325
-
326
- Raises:
327
- ConfigValueError: 当值无法转换为指定类型时
328
- """
245
+ def _convert_value(self, value: str, target_type: Type[T], file_path: Optional[Union[str, Path]] = None, key: Optional[str] = None) -> T:
246
+ """转换配置值到指定类型"""
329
247
  try:
330
248
  if target_type == bool:
331
249
  return bool(value.lower() in ('true', 'yes', '1', 'on'))
332
250
  elif target_type == list:
333
- # 支持多种分隔符的列表
334
251
  if not value.strip():
335
252
  return []
336
- # 尝试不同的分隔符
337
253
  for sep in [',', ';', '|', ' ']:
338
254
  if sep in value:
339
255
  return [item.strip() for item in value.split(sep) if item.strip()]
340
- # 如果没有分隔符,则作为单个元素返回
341
256
  return [value.strip()]
342
257
  elif target_type == tuple:
343
- # 支持元组类型
344
258
  if not value.strip():
345
259
  return ()
346
- # 尝试不同的分隔符
347
260
  for sep in [',', ';', '|', ' ']:
348
261
  if sep in value:
349
262
  return tuple(item.strip() for item in value.split(sep) if item.strip())
350
- # 如果没有分隔符,则作为单个元素返回
351
263
  return (value.strip(),)
352
- elif target_type == set:
353
- # 支持集合类型
264
+ elif target_type == set or target_type == frozenset:
354
265
  if not value.strip():
355
- return set()
356
- # 尝试不同的分隔符
266
+ return set() if target_type == set else frozenset()
357
267
  for sep in [',', ';', '|', ' ']:
358
268
  if sep in value:
359
- return {item.strip() for item in value.split(sep) if item.strip()}
360
- # 如果没有分隔符,则作为单个元素返回
361
- return {value.strip()}
269
+ items = [item.strip() for item in value.split(sep) if item.strip()]
270
+ return set(items) if target_type == set else frozenset(items)
271
+ return set([value.strip()]) if target_type == set else frozenset([value.strip()])
362
272
  elif target_type == dict:
363
- # 支持字典类型,格式:key1=value1,key2=value2
364
273
  if not value.strip():
365
274
  return {}
366
275
  result = {}
367
- # 尝试不同的分隔符
368
276
  for sep in [',', ';', '|']:
369
277
  if sep in value:
370
278
  pairs = [pair.strip() for pair in value.split(sep) if pair.strip()]
371
279
  for pair in pairs:
372
280
  if '=' in pair:
373
- key, val = pair.split('=', 1)
374
- result[key.strip()] = val.strip()
281
+ key_, val = pair.split('=', 1)
282
+ result[key_.strip()] = val.strip()
375
283
  return result
376
- # 如果没有分隔符,尝试单个键值对
377
284
  if '=' in value:
378
- key, val = value.split('=', 1)
379
- return {key.strip(): val.strip()}
285
+ key_, val = value.split('=', 1)
286
+ return {key_.strip(): val.strip()}
380
287
  return {}
381
288
  elif target_type == int:
382
- # 支持十六进制、八进制、二进制
383
289
  value = value.strip().lower()
384
290
  if value.startswith('0x'):
385
291
  return int(value, 16)
@@ -396,12 +302,7 @@ class ConfigParser:
396
302
  return value.encode('utf-8')
397
303
  elif target_type == bytearray:
398
304
  return bytearray(value.encode('utf-8'))
399
- elif target_type == set:
400
- return set(value.split(','))
401
- elif target_type == frozenset:
402
- return frozenset(value.split(','))
403
305
  elif target_type == range:
404
- # 支持 range 类型,格式:start:stop:step 或 start:stop
405
306
  parts = value.split(':')
406
307
  if len(parts) == 2:
407
308
  return range(int(parts[0]), int(parts[1]))
@@ -410,81 +311,39 @@ class ConfigParser:
410
311
  raise ValueError("Invalid range format")
411
312
  return target_type(value)
412
313
  except (ValueError, TypeError) as e:
413
- raise ConfigValueError(
414
- f"无法将值 '{value}' 转换为类型 {target_type.__name__}",
415
- file_path=None,
416
- key=None
417
- )
314
+ raise ConfigException.conversion_error(value, target_type, file_path, key=key)
418
315
 
419
- def get_value(self, file_path: Optional[Union[str, Path]] = None, key: str = None,
420
- section: Optional[str] = None, default: Any = None,
421
- value_type: Optional[Type[T]] = None) -> T:
422
- """获取指定配置项的值
423
-
424
- Args:
425
- file_path: 配置文件路径,如果为None则使用当前打开的文件
426
- key: 配置键
427
- section: 配置节名称,如果为None则使用默认节
428
- default: 当配置项不存在时返回的默认值
429
- value_type: 期望的值的类型
430
-
431
- Returns:
432
- T: 配置值
433
-
434
- Raises:
435
- ConfigSectionNotFoundError: 当指定的节不存在且未提供默认值时
436
- ConfigKeyNotFoundError: 当指定的键不存在且未提供默认值时
437
- ConfigValueError: 当值无法转换为指定类型时
438
- """
316
+ def get_value(self, section: Optional[str] = None, key: str = None,
317
+ default: Any = None, value_type: Optional[Type[T]] = None,
318
+ file_path: Optional[Union[str, Path]] = None) -> T:
319
+ """获取指定配置项的值"""
439
320
  if file_path is None:
440
321
  self._ensure_file_open()
441
322
  file_path = self._current_file
442
323
  if not self._validate_key(key):
443
- raise ConfigValueError(f"无效的键名: {key}", file_path=file_path, key=key)
444
-
324
+ raise ConfigException.invalid_key_error(key, file_path, section)
445
325
  config = self.read(file_path)
446
326
  section = section or self.options.default_section
447
327
  normalized_section = self._normalize_section(section)
448
-
449
- # 获取原始节名称
450
328
  original_section = self._get_original_section(str(file_path), normalized_section)
451
329
  if original_section is None:
452
330
  if default is not None:
453
331
  return default
454
- raise ConfigSectionNotFoundError(file_path, section)
455
-
332
+ raise ConfigException.section_not_found(file_path, section)
456
333
  if key not in config[original_section]:
457
334
  if default is not None:
458
335
  return default
459
- raise ConfigKeyNotFoundError(file_path, original_section, key)
460
-
336
+ raise ConfigException.key_not_found(file_path, original_section, key)
461
337
  value = config[original_section][key]
462
-
463
338
  if value_type is not None:
464
- return self._convert_value(value, value_type)
465
-
339
+ return self._convert_value(value, value_type, file_path=file_path, key=key)
466
340
  return value
467
341
 
468
- def get_values(self, keys: List[Tuple[str, str]],
469
- file_path: Optional[Union[str, Path]] = None,
342
+ def get_values(self, section: Optional[str] = None, keys: List[Tuple[str, str]] = None,
470
343
  defaults: Optional[Dict[str, Any]] = None,
471
- value_types: Optional[Dict[str, Type]] = None) -> Dict[str, Any]:
472
- """批量获取多个配置项的值
473
-
474
- Args:
475
- keys: 配置项列表,每个元素为 (section, key) 元组
476
- file_path: 配置文件路径,如果为None则使用当前打开的文件
477
- defaults: 默认值字典,格式为 {key: default_value}
478
- value_types: 值类型字典,格式为 {key: type}
479
-
480
- Returns:
481
- Dict[str, Any]: 配置值字典,格式为 {key: value}
482
-
483
- Raises:
484
- ConfigSectionNotFoundError: 当指定的节不存在且未提供默认值时
485
- ConfigKeyNotFoundError: 当指定的键不存在且未提供默认值时
486
- ConfigValueError: 当值无法转换为指定类型时
487
- """
344
+ value_types: Optional[Dict[str, Type]] = None,
345
+ file_path: Optional[Union[str, Path]] = None) -> Dict[str, Any]:
346
+ """批量获取多个配置项的值"""
488
347
  if file_path is None:
489
348
  self._ensure_file_open()
490
349
  file_path = self._current_file
@@ -495,14 +354,15 @@ class ConfigParser:
495
354
  for section, key in keys:
496
355
  try:
497
356
  value = self.get_value(
498
- file_path=file_path,
499
- key=key,
500
357
  section=section,
358
+ key=key,
501
359
  default=defaults.get(key),
502
- value_type=value_types.get(key)
360
+ value_type=value_types.get(key),
361
+ file_path=file_path
503
362
  )
504
363
  result[key] = value
505
- except (ConfigSectionNotFoundError, ConfigKeyNotFoundError) as e:
364
+ except ConfigException as e:
365
+ logger.error(f"读取配置项失败: section={section}, key={key}, error={e}")
506
366
  if key in defaults:
507
367
  result[key] = defaults[key]
508
368
  else:
@@ -510,28 +370,11 @@ class ConfigParser:
510
370
 
511
371
  return result
512
372
 
513
- def get_section_values(self, keys: List[str],
514
- section: Optional[str] = None,
515
- file_path: Optional[Union[str, Path]] = None,
373
+ def get_section_values(self, section: Optional[str] = None, keys: List[str] = None,
516
374
  defaults: Optional[Dict[str, Any]] = None,
517
- value_types: Optional[Dict[str, Type]] = None) -> Tuple[Any, ...]:
518
- """获取指定节点下多个键的值元组
519
-
520
- Args:
521
- keys: 要获取的键列表
522
- section: 配置节名称,默认为 DEFAULT
523
- file_path: 配置文件路径,如果为None则使用当前打开的文件
524
- defaults: 默认值字典,格式为 {key: default_value}
525
- value_types: 值类型字典,格式为 {key: type}
526
-
527
- Returns:
528
- Tuple[Any, ...]: 按键列表顺序返回的值元组
529
-
530
- Raises:
531
- ConfigSectionNotFoundError: 当指定的节不存在且未提供默认值时
532
- ConfigKeyNotFoundError: 当指定的键不存在且未提供默认值时
533
- ConfigValueError: 当值无法转换为指定类型时
534
- """
375
+ value_types: Optional[Dict[str, Type]] = None,
376
+ file_path: Optional[Union[str, Path]] = None) -> Tuple[Any, ...]:
377
+ """获取指定节点下多个键的值元组"""
535
378
  if file_path is None:
536
379
  self._ensure_file_open()
537
380
  file_path = self._current_file
@@ -542,14 +385,15 @@ class ConfigParser:
542
385
  for key in keys:
543
386
  try:
544
387
  value = self.get_value(
545
- file_path=file_path,
546
- key=key,
547
388
  section=section,
389
+ key=key,
548
390
  default=defaults.get(key),
549
- value_type=value_types.get(key)
391
+ value_type=value_types.get(key),
392
+ file_path=file_path
550
393
  )
551
394
  result.append(value)
552
- except (ConfigSectionNotFoundError, ConfigKeyNotFoundError) as e:
395
+ except ConfigException as e:
396
+ logger.error(f"读取配置项失败: section={section}, key={key}, error={e}")
553
397
  if key in defaults:
554
398
  result.append(defaults[key])
555
399
  else:
@@ -557,41 +401,23 @@ class ConfigParser:
557
401
 
558
402
  return tuple(result)
559
403
 
560
- def set_value(self, key: str, value: Any,
561
- section: Optional[str] = None,
562
- file_path: Optional[Union[str, Path]] = None,
563
- value_type: Optional[Type] = None) -> None:
564
- """设置指定配置项的值,保持原始文件的格式和注释
565
-
566
- Args:
567
- key: 配置键
568
- value: 要设置的值
569
- section: 配置节名称,如果为None则使用默认节
570
- file_path: 配置文件路径,如果为None则使用当前打开的文件
571
- value_type: 值的类型,用于验证和转换
572
-
573
- Raises:
574
- ConfigValueError: 当值无法转换为指定类型时
575
- ConfigError: 当其他配置错误发生时
576
- """
404
+ def set_value(self, section: Optional[str] = None, key: str = None, value: Any = None,
405
+ value_type: Optional[Type] = None,
406
+ file_path: Optional[Union[str, Path]] = None) -> None:
407
+ """设置指定配置项的值"""
577
408
  if file_path is None:
578
409
  self._ensure_file_open()
579
410
  file_path = self._current_file
580
411
  if not self._validate_key(key):
581
- raise ConfigValueError(f"无效的键名: {key}", file_path=file_path, key=key)
582
-
583
- # 读取原始文件內容
412
+ raise ConfigException.invalid_key_error(key, file_path, section)
413
+ section = section or self.options.default_section
584
414
  original_lines = []
585
415
  if file_path.exists():
586
416
  with open(file_path, 'r', encoding=self.options.encoding) as file:
587
417
  original_lines = file.readlines()
588
-
589
- # 读取当前配置
590
418
  config = self.read(file_path)
591
-
592
419
  if section not in config:
593
420
  config[section] = {}
594
-
595
421
  if value_type is not None:
596
422
  try:
597
423
  if value_type == bool:
@@ -602,109 +428,106 @@ class ConfigParser:
602
428
  else:
603
429
  value = value_type(value)
604
430
  except (ValueError, TypeError) as e:
605
- raise ConfigValueError(
606
- f"无法将值 '{value}' 转换为类型 {value_type.__name__}",
607
- file_path=file_path,
608
- section=section,
609
- key=key
610
- )
611
-
431
+ raise ConfigException.conversion_error(value, value_type, file_path, section=section, key=key)
612
432
  if isinstance(value, bool):
613
433
  value = str(value).lower()
614
434
  else:
615
435
  value = str(value)
616
-
617
- # 更新配置
618
436
  config[section][key] = value
619
-
620
- # 写入文件,保持原始格式
621
437
  try:
622
438
  file_path.parent.mkdir(parents=True, exist_ok=True)
623
-
624
439
  with open(file_path, 'w', encoding=self.options.encoding) as file:
625
440
  current_section = self.options.default_section
626
- section_separators = {} # 用于存储每个section使用的分隔符
627
-
628
- # 解析原始文件,提取格式信息
441
+ section_separators = {}
629
442
  for line in original_lines:
630
443
  stripped_line = line.strip()
631
-
632
444
  if not stripped_line:
633
- file.write(line) # 保持空行
445
+ file.write(line)
634
446
  continue
635
-
636
447
  if stripped_line.startswith('[') and stripped_line.endswith(']'):
637
448
  current_section = stripped_line[1:-1]
638
- file.write(line) # 保持节标记的原始格式
449
+ file.write(line)
639
450
  continue
640
-
641
451
  if self._is_comment_line(stripped_line):
642
- file.write(line) # 保持注释的原始格式
452
+ file.write(line)
643
453
  continue
644
-
645
454
  key_value = self._split_key_value(stripped_line)
646
455
  if key_value:
647
456
  orig_key, orig_value = key_value
648
- # 检测使用的分隔符
649
457
  for sep in self.options.separators:
650
458
  if sep in line:
651
459
  section_separators.setdefault(current_section, {})[orig_key] = sep
652
460
  break
653
-
654
- # 如果是当前要修改的键,则写入新值
655
461
  if current_section == section and orig_key == key:
656
462
  separator = section_separators.get(current_section, {}).get(orig_key, self.options.separators[0])
657
- # 提取行尾注释
658
463
  comment = ''
659
464
  for style in self.options.comment_styles:
660
465
  comment_match = re.search(fr'\s+{re.escape(style.value)}.*$', line)
661
466
  if comment_match:
662
467
  comment = comment_match.group(0)
663
468
  break
664
- # 写入新值并保留注释
665
469
  file.write(f'{key}{separator}{value}{comment}\n')
666
470
  else:
667
- file.write(line) # 保持其他行的原始格式
471
+ file.write(line)
668
472
  else:
669
- file.write(line) # 保持无法解析的行的原始格式
670
-
671
- # 如果section不存在,则添加新的section
473
+ file.write(line)
672
474
  if section not in [line.strip()[1:-1] for line in original_lines if line.strip().startswith('[') and line.strip().endswith(']')]:
673
475
  file.write(f'\n[{section}]\n')
674
476
  file.write(f'{key}={value}\n')
675
-
676
477
  self._clear_cache(str(file_path))
677
-
678
478
  except Exception as e:
679
- raise ConfigWriteError(file_path, e)
479
+ raise ConfigException.write_error(file_path, e)
480
+
481
+ def set_values(self, section: Optional[str] = None, values: Dict[str, Any] = None,
482
+ value_types: Optional[Dict[str, Type]] = None,
483
+ file_path: Optional[Union[str, Path]] = None) -> None:
484
+ """批量设置多个配置项的值"""
485
+ for key, value in values.items():
486
+ value_type = value_types.get(key) if value_types else None
487
+ self.set_value(section, key, value, value_type, file_path)
488
+
489
+ def validate_config(self, section: Optional[str] = None, schema: Dict[str, Type] = None,
490
+ file_path: Optional[Union[str, Path]] = None) -> bool:
491
+ """验证配置是否符合指定的模式"""
492
+ config = self.read(file_path)
493
+ if section:
494
+ if section not in config:
495
+ return False
496
+ for key, expected_type in schema.items():
497
+ if key not in config[section]:
498
+ return False
499
+ try:
500
+ self._convert_value(config[section][key], expected_type)
501
+ except ConfigException:
502
+ return False
503
+ else:
504
+ for section, keys in schema.items():
505
+ if section not in config:
506
+ return False
507
+ for key, expected_type in keys.items():
508
+ if key not in config[section]:
509
+ return False
510
+ try:
511
+ self._convert_value(config[section][key], expected_type)
512
+ except ConfigException:
513
+ return False
514
+ return True
680
515
 
681
516
  def read(self, file_path: Optional[Union[str, Path]] = None) -> Dict[str, Any]:
682
- """读取配置文件内容
683
-
684
- Args:
685
- file_path: 配置文件路径,如果为None则使用当前打开的文件
686
-
687
- Returns:
688
- Dict[str, Any]: 配置字典,格式为 {section: {key: value}}
689
-
690
- Raises:
691
- ConfigFileNotFoundError: 当配置文件不存在且未启用自动创建时
692
- ConfigReadError: 当读取配置文件失败时
693
- """
517
+ """读取配置文件内容"""
694
518
  if file_path is None:
695
519
  self._ensure_file_open()
696
520
  file_path = self._current_file
697
521
  else:
698
- file_path = Path(file_path) # 确保 file_path 是 Path 对象
522
+ file_path = Path(file_path)
699
523
 
700
- # 检查缓存
701
524
  cached_config = self._get_cached_config(str(file_path))
702
525
  if cached_config is not None:
703
526
  return cached_config
704
527
 
705
528
  if not file_path.exists():
706
529
  if not self.options.auto_create:
707
- raise ConfigFileNotFoundError(file_path)
530
+ raise ConfigException.file_not_found(file_path)
708
531
  logger.info(f'配置文件不存在,将创建: {file_path}')
709
532
  file_path.parent.mkdir(parents=True, exist_ok=True)
710
533
  file_path.touch()
@@ -727,11 +550,7 @@ class ConfigParser:
727
550
  if stripped_line.startswith('[') and stripped_line.endswith(']'):
728
551
  current_section = stripped_line[1:-1]
729
552
  if not self._validate_key(current_section):
730
- raise ConfigValueError(
731
- f"无效的节名: {current_section}",
732
- file_path=file_path,
733
- section=current_section
734
- )
553
+ raise ConfigException.invalid_section_error(current_section, file_path)
735
554
  self._update_section_map(str(file_path), current_section)
736
555
  if current_section not in config:
737
556
  config[current_section] = {}
@@ -744,12 +563,7 @@ class ConfigParser:
744
563
  if key_value:
745
564
  key, value = key_value
746
565
  if not self._validate_key(key):
747
- raise ConfigValueError(
748
- f"无效的键名: {key}",
749
- file_path=file_path,
750
- section=current_section,
751
- key=key
752
- )
566
+ raise ConfigException.invalid_key_error(key, file_path, current_section)
753
567
  value, comment = self._extract_comment(value)
754
568
 
755
569
  if self.options.strip_values:
@@ -766,51 +580,141 @@ class ConfigParser:
766
580
  return config
767
581
 
768
582
  except Exception as e:
769
- raise ConfigReadError(file_path, e)
770
-
583
+ raise ConfigException.read_error(file_path, e)
584
+
771
585
 
772
586
  def main() -> None:
773
- # 使用示例
774
- config_file = Path('/Users/xigua/spd.txt')
775
-
587
+ """示例用法"""
588
+ config_file = Path('/Users/xigua/spd_副本.txt')
589
+
590
+ # 创建配置解析器实例
591
+ parser = ConfigParser()
592
+ # 设置配置选项
593
+ options = ConfigOptions(
594
+ comment_styles=[CommentStyle.HASH, CommentStyle.DOUBLE_SLASH],
595
+ encoding='utf-8',
596
+ auto_create=True,
597
+ strip_values=True,
598
+ preserve_comments=True,
599
+ default_section='DEFAULT',
600
+ separators=['=', ':', ':'],
601
+ cache_ttl=300,
602
+ validate_keys=True,
603
+ key_pattern=r'^[a-zA-Z0-9_\-\.]+$',
604
+ case_sensitive=False
605
+ )
606
+ parser = ConfigParser(options)
607
+
776
608
  # 方式1:使用上下文管理器
777
609
  with ConfigParser() as parser:
778
610
  parser.open(config_file)
611
+ # 读取配置 - 最常用的参数放在前面
779
612
  host, port, username, password = parser.get_section_values(
780
- keys=['host', 'port', 'username', 'password'],
781
- section='mysql'
613
+ section='mysql', # 最常用的section参数放在前面
614
+ keys=['host', 'port', 'username', 'password']
782
615
  )
783
- print("方式1结果:", host, port, username, password)
784
-
616
+ print("2.1 使用上下文管理器 读取结果:", host, port, username, password)
785
617
  # 修改配置
786
- parser.set_value('host', 'localhost', section='mysql')
787
- parser.set_value('port', 3306, section='mysql')
788
-
789
- # # 读取整个配置
790
- # config = parser.read()
791
- # print("\n当前配置:")
792
- # for section, items in config.items():
793
- # print(f"\n[{section}]")
794
- # for key, value in items.items():
795
- # print(f"{key} = {value}")
796
-
618
+ parser.set_value(
619
+ section='mysql',
620
+ key='username',
621
+ value='root'
622
+ )
623
+ parser.set_value(
624
+ section='mysql',
625
+ key='port',
626
+ value=3306,
627
+ value_type=int
628
+ )
629
+
797
630
  # 方式2:链式调用
798
631
  parser = ConfigParser()
799
632
  host, port, username, password = parser.open(config_file).get_section_values(
800
- keys=['host', 'port', 'username', 'password'],
801
- section='mysql'
633
+ section='mysql',
634
+ keys=['host', 'port', 'username', 'password']
802
635
  )
803
- print("\n方式2结果:", host, port, username, password)
804
-
636
+ print("2.2 链式调用 读取结果:", host, port, username, password)
637
+
805
638
  # 方式3:传统方式
806
639
  parser = ConfigParser()
807
640
  host, port, username, password = parser.get_section_values(
808
- file_path=config_file,
809
641
  section='mysql',
810
- keys=['host', 'port', 'username', 'password']
642
+ keys=['host', 'port', 'username', 'password'],
643
+ file_path=config_file # 文件路径参数放在最后
644
+ )
645
+ print("2.3 传统方式 读取结果:", host, port, username, password)
646
+
647
+ # 3.1 读取单个值 - 最常用的参数放在前面
648
+ value = parser.get_value(
649
+ section='mysql',
650
+ key='host',
651
+ default='localhost', # 默认值参数放在中间
652
+ value_type=str,
653
+ file_path=config_file
654
+ )
655
+ print("3.1 单个值读取结果:", value)
656
+
657
+ # 3.2 批量读取多个值 - 最常用的参数放在前面
658
+ values = parser.get_values(
659
+ section='mysql',
660
+ keys=[('host', 'host'), ('host', 'port')],
661
+ defaults={'host': 'localhost', 'port': 3306},
662
+ value_types={'port': int},
663
+ file_path=config_file
664
+ )
665
+ print("3.2 批量读取多个值结果:", values)
666
+
667
+ # 3.3 读取整个配置
668
+ config = parser.read(config_file)
669
+ print("3.3 读取完整配置:", config)
670
+
671
+ # 4.1 写入单个值 - 最常用的参数放在前面
672
+ parser.set_value(
673
+ section='mysql',
674
+ key='host',
675
+ value='127.0.0.1',
676
+ value_type=str,
677
+ file_path=config_file
678
+ )
679
+ print("4.1 写入单个值结果:", config_file)
680
+
681
+ # 4.2 写入不同类型的数据 - 最常用的参数放在前面
682
+ parser.open(config_file)
683
+ parser.set_value(
684
+ section='mysql',
685
+ key='port',
686
+ value=3306,
687
+ value_type=int
688
+ )
689
+ parser.set_value(
690
+ section='mysql',
691
+ key='debug',
692
+ value=True,
693
+ value_type=bool
811
694
  )
812
- print("\n方式3结果:", host, port, username, password)
695
+ parser.set_value(
696
+ section='mysql',
697
+ key='servers',
698
+ value=['server1', 'server2'],
699
+ value_type=list
700
+ )
701
+ print("4.2 写入不同类型的数据")
702
+
703
+ try:
704
+ # 尝试读取不存在的配置项 - 最常用的参数放在前面
705
+ parser.get_value(
706
+ section='mysql',
707
+ key='nonexistent'
708
+ )
709
+ except ConfigException as e:
710
+ if "配置键不存在" in str(e):
711
+ print("5.1 错误处理 配置键不存在错误:", e)
712
+ elif "配置节不存在" in str(e):
713
+ print("5.2 错误处理 配置节不存在错误:", e)
714
+ else:
715
+ print("5.3 错误处理 配置错误:", e)
813
716
 
814
717
 
815
718
  if __name__ == '__main__':
816
719
  main()
720
+