mdbq 4.0.23__py3-none-any.whl → 4.0.24__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 +1 -1
- mdbq/myconf/myconf.py +278 -374
- mdbq/myconf/{myconf2.py → myconf_bak.py} +296 -74
- {mdbq-4.0.23.dist-info → mdbq-4.0.24.dist-info}/METADATA +1 -1
- {mdbq-4.0.23.dist-info → mdbq-4.0.24.dist-info}/RECORD +7 -7
- {mdbq-4.0.23.dist-info → mdbq-4.0.24.dist-info}/WHEEL +0 -0
- {mdbq-4.0.23.dist-info → mdbq-4.0.24.dist-info}/top_level.txt +0 -0
@@ -22,7 +22,14 @@ T = TypeVar('T') # 类型变量
|
|
22
22
|
|
23
23
|
|
24
24
|
class ConfigError(Exception):
|
25
|
-
"""配置相关的基础异常类
|
25
|
+
"""配置相关的基础异常类
|
26
|
+
|
27
|
+
Attributes:
|
28
|
+
message: 错误消息
|
29
|
+
file_path: 配置文件路径
|
30
|
+
section: 配置节名称
|
31
|
+
key: 配置键名称
|
32
|
+
"""
|
26
33
|
def __init__(self, message: str, file_path: Optional[Union[str, Path]] = None,
|
27
34
|
section: Optional[str] = None, key: Optional[str] = None):
|
28
35
|
self.message = message
|
@@ -44,13 +51,17 @@ class ConfigError(Exception):
|
|
44
51
|
|
45
52
|
|
46
53
|
class ConfigFileNotFoundError(ConfigError):
|
47
|
-
"""
|
54
|
+
"""当指定的配置文件不存在时抛出的异常"""
|
48
55
|
def __init__(self, file_path: Union[str, Path]):
|
49
56
|
super().__init__("配置文件不存在", file_path=file_path)
|
50
57
|
|
51
58
|
|
52
59
|
class ConfigReadError(ConfigError):
|
53
|
-
"""
|
60
|
+
"""当读取配置文件失败时抛出的异常
|
61
|
+
|
62
|
+
Attributes:
|
63
|
+
original_error: 原始错误对象
|
64
|
+
"""
|
54
65
|
def __init__(self, file_path: Union[str, Path], original_error: Exception):
|
55
66
|
super().__init__(
|
56
67
|
f"读取配置文件失败: {str(original_error)}",
|
@@ -60,7 +71,11 @@ class ConfigReadError(ConfigError):
|
|
60
71
|
|
61
72
|
|
62
73
|
class ConfigWriteError(ConfigError):
|
63
|
-
"""
|
74
|
+
"""当写入配置文件失败时抛出的异常
|
75
|
+
|
76
|
+
Attributes:
|
77
|
+
original_error: 原始错误对象
|
78
|
+
"""
|
64
79
|
def __init__(self, file_path: Union[str, Path], original_error: Exception):
|
65
80
|
super().__init__(
|
66
81
|
f"写入配置文件失败: {str(original_error)}",
|
@@ -70,14 +85,14 @@ class ConfigWriteError(ConfigError):
|
|
70
85
|
|
71
86
|
|
72
87
|
class ConfigValueError(ConfigError):
|
73
|
-
"""
|
88
|
+
"""当配置值无效时抛出的异常"""
|
74
89
|
def __init__(self, message: str, file_path: Union[str, Path],
|
75
90
|
section: Optional[str] = None, key: Optional[str] = None):
|
76
91
|
super().__init__(message, file_path=file_path, section=section, key=key)
|
77
92
|
|
78
93
|
|
79
94
|
class ConfigSectionNotFoundError(ConfigError):
|
80
|
-
"""
|
95
|
+
"""当指定的配置节不存在时抛出的异常"""
|
81
96
|
def __init__(self, file_path: Union[str, Path], section: str):
|
82
97
|
super().__init__(
|
83
98
|
f"配置节不存在",
|
@@ -87,7 +102,7 @@ class ConfigSectionNotFoundError(ConfigError):
|
|
87
102
|
|
88
103
|
|
89
104
|
class ConfigKeyNotFoundError(ConfigError):
|
90
|
-
"""
|
105
|
+
"""当指定的配置键不存在时抛出的异常"""
|
91
106
|
def __init__(self, file_path: Union[str, Path], section: str, key: str):
|
92
107
|
super().__init__(
|
93
108
|
f"配置键不存在",
|
@@ -106,7 +121,21 @@ class CommentStyle(Enum):
|
|
106
121
|
|
107
122
|
@dataclass
|
108
123
|
class ConfigOptions:
|
109
|
-
"""
|
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
|
+
"""
|
110
139
|
comment_styles: List[CommentStyle] = field(default_factory=lambda: [CommentStyle.HASH, CommentStyle.DOUBLE_SLASH])
|
111
140
|
encoding: str = 'utf-8'
|
112
141
|
auto_create: bool = False
|
@@ -121,26 +150,57 @@ class ConfigOptions:
|
|
121
150
|
|
122
151
|
|
123
152
|
class ConfigParser:
|
124
|
-
"""
|
153
|
+
"""配置文件解析器,用于读取和写入配置文件
|
154
|
+
|
155
|
+
Attributes:
|
156
|
+
options: 解析器配置选项
|
157
|
+
_config_cache: 配置缓存,用于存储已读取的配置
|
158
|
+
_cache_timestamps: 缓存时间戳,用于管理缓存过期
|
159
|
+
_comments_cache: 注释缓存,用于存储每个配置节的注释
|
160
|
+
_section_map: 用于存储大小写映射
|
161
|
+
_current_file: 当前正在处理的文件路径
|
162
|
+
"""
|
125
163
|
|
126
164
|
def __init__(self, options: Optional[ConfigOptions] = None):
|
127
165
|
self.options = options or ConfigOptions()
|
128
166
|
self._config_cache: Dict[str, Dict[str, Any]] = {}
|
129
167
|
self._cache_timestamps: Dict[str, float] = {}
|
130
168
|
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
|
169
|
+
self._section_map: Dict[str, Dict[str, str]] = {} # 用于存储大小写映射
|
170
|
+
self._current_file: Optional[Path] = None # 当前正在处理的文件路径
|
133
171
|
|
134
172
|
def __enter__(self) -> 'ConfigParser':
|
173
|
+
"""进入上下文管理器
|
174
|
+
|
175
|
+
Returns:
|
176
|
+
ConfigParser: 返回当前实例
|
177
|
+
"""
|
135
178
|
return self
|
136
179
|
|
137
180
|
def __exit__(self, exc_type: Optional[Type[BaseException]],
|
138
181
|
exc_val: Optional[BaseException],
|
139
182
|
exc_tb: Optional[Any]) -> None:
|
183
|
+
"""退出上下文管理器
|
184
|
+
|
185
|
+
Args:
|
186
|
+
exc_type: 异常类型
|
187
|
+
exc_val: 异常值
|
188
|
+
exc_tb: 异常追踪信息
|
189
|
+
"""
|
140
190
|
self._current_file = None
|
141
191
|
|
142
192
|
def open(self, file_path: Union[str, Path]) -> 'ConfigParser':
|
143
|
-
"""打开配置文件
|
193
|
+
"""打开配置文件
|
194
|
+
|
195
|
+
Args:
|
196
|
+
file_path: 配置文件路径
|
197
|
+
|
198
|
+
Returns:
|
199
|
+
ConfigParser: 返回当前实例,支持链式调用
|
200
|
+
|
201
|
+
Raises:
|
202
|
+
ConfigFileNotFoundError: 当配置文件不存在且未启用自动创建时
|
203
|
+
"""
|
144
204
|
file_path = Path(file_path)
|
145
205
|
if not file_path.exists() and not self.options.auto_create:
|
146
206
|
raise ConfigFileNotFoundError(file_path)
|
@@ -148,17 +208,25 @@ class ConfigParser:
|
|
148
208
|
return self
|
149
209
|
|
150
210
|
def _ensure_file_open(self) -> None:
|
151
|
-
"""确保文件已打开
|
211
|
+
"""确保文件已打开
|
212
|
+
|
213
|
+
Raises:
|
214
|
+
ConfigError: 当文件未打开时
|
215
|
+
"""
|
152
216
|
if self._current_file is None:
|
153
217
|
raise ConfigError("未打开任何配置文件,请先调用 open() 方法")
|
154
218
|
|
155
219
|
def _is_comment_line(self, line: str) -> bool:
|
156
|
-
"""
|
220
|
+
"""判断一行是否为注释行"""
|
157
221
|
stripped = line.strip()
|
158
222
|
return any(stripped.startswith(style.value) for style in self.options.comment_styles)
|
159
223
|
|
160
224
|
def _extract_comment(self, line: str) -> Tuple[str, str]:
|
161
|
-
"""从行中提取注释
|
225
|
+
"""从行中提取注释
|
226
|
+
|
227
|
+
Returns:
|
228
|
+
Tuple[str, str]: (去除注释后的行内容, 注释内容)
|
229
|
+
"""
|
162
230
|
for style in self.options.comment_styles:
|
163
231
|
comment_match = re.search(fr'\s+{re.escape(style.value)}.*$', line)
|
164
232
|
if comment_match:
|
@@ -166,7 +234,14 @@ class ConfigParser:
|
|
166
234
|
return line.strip(), ''
|
167
235
|
|
168
236
|
def _split_key_value(self, line: str) -> Optional[Tuple[str, str]]:
|
169
|
-
"""分割配置行为键值对
|
237
|
+
"""分割配置行为键值对
|
238
|
+
|
239
|
+
Args:
|
240
|
+
line: 要分割的配置行
|
241
|
+
|
242
|
+
Returns:
|
243
|
+
Optional[Tuple[str, str]]: 键值对元组,如果无法分割则返回None
|
244
|
+
"""
|
170
245
|
for sep in self.options.separators:
|
171
246
|
if sep in line:
|
172
247
|
key_part, value_part = line.split(sep, 1)
|
@@ -188,8 +263,11 @@ class ConfigParser:
|
|
188
263
|
return bool(re.match(self.options.key_pattern, key))
|
189
264
|
|
190
265
|
def _get_cached_config(self, file_path: str) -> Optional[Dict[str, Any]]:
|
191
|
-
"""
|
192
|
-
if file_path not in self._config_cache
|
266
|
+
"""获取缓存的配置,如果过期则返回None"""
|
267
|
+
if file_path not in self._config_cache:
|
268
|
+
return None
|
269
|
+
|
270
|
+
if file_path not in self._cache_timestamps:
|
193
271
|
return None
|
194
272
|
|
195
273
|
if time.time() - self._cache_timestamps[file_path] > self.options.cache_ttl:
|
@@ -203,8 +281,10 @@ class ConfigParser:
|
|
203
281
|
self._cache_timestamps[file_path] = time.time()
|
204
282
|
|
205
283
|
def _normalize_section(self, section: str) -> str:
|
206
|
-
"""
|
207
|
-
|
284
|
+
"""标准化节名称(处理大小写)"""
|
285
|
+
if self.options.case_sensitive:
|
286
|
+
return section
|
287
|
+
return section.lower()
|
208
288
|
|
209
289
|
def _get_original_section(self, file_path: str, normalized_section: str) -> Optional[str]:
|
210
290
|
"""获取原始节名称"""
|
@@ -233,50 +313,73 @@ class ConfigParser:
|
|
233
313
|
self._comments_cache.clear()
|
234
314
|
self._section_map.clear()
|
235
315
|
|
236
|
-
def _convert_value(self, value: str, target_type: Type[T]
|
237
|
-
"""转换配置值到指定类型
|
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
|
+
"""
|
238
329
|
try:
|
239
330
|
if target_type == bool:
|
240
331
|
return bool(value.lower() in ('true', 'yes', '1', 'on'))
|
241
332
|
elif target_type == list:
|
333
|
+
# 支持多种分隔符的列表
|
242
334
|
if not value.strip():
|
243
335
|
return []
|
336
|
+
# 尝试不同的分隔符
|
244
337
|
for sep in [',', ';', '|', ' ']:
|
245
338
|
if sep in value:
|
246
339
|
return [item.strip() for item in value.split(sep) if item.strip()]
|
340
|
+
# 如果没有分隔符,则作为单个元素返回
|
247
341
|
return [value.strip()]
|
248
342
|
elif target_type == tuple:
|
343
|
+
# 支持元组类型
|
249
344
|
if not value.strip():
|
250
345
|
return ()
|
346
|
+
# 尝试不同的分隔符
|
251
347
|
for sep in [',', ';', '|', ' ']:
|
252
348
|
if sep in value:
|
253
349
|
return tuple(item.strip() for item in value.split(sep) if item.strip())
|
350
|
+
# 如果没有分隔符,则作为单个元素返回
|
254
351
|
return (value.strip(),)
|
255
|
-
elif target_type == set
|
352
|
+
elif target_type == set:
|
353
|
+
# 支持集合类型
|
256
354
|
if not value.strip():
|
257
|
-
return set()
|
355
|
+
return set()
|
356
|
+
# 尝试不同的分隔符
|
258
357
|
for sep in [',', ';', '|', ' ']:
|
259
358
|
if sep in value:
|
260
|
-
|
261
|
-
|
262
|
-
return
|
359
|
+
return {item.strip() for item in value.split(sep) if item.strip()}
|
360
|
+
# 如果没有分隔符,则作为单个元素返回
|
361
|
+
return {value.strip()}
|
263
362
|
elif target_type == dict:
|
363
|
+
# 支持字典类型,格式:key1=value1,key2=value2
|
264
364
|
if not value.strip():
|
265
365
|
return {}
|
266
366
|
result = {}
|
367
|
+
# 尝试不同的分隔符
|
267
368
|
for sep in [',', ';', '|']:
|
268
369
|
if sep in value:
|
269
370
|
pairs = [pair.strip() for pair in value.split(sep) if pair.strip()]
|
270
371
|
for pair in pairs:
|
271
372
|
if '=' in pair:
|
272
|
-
|
273
|
-
result[
|
373
|
+
key, val = pair.split('=', 1)
|
374
|
+
result[key.strip()] = val.strip()
|
274
375
|
return result
|
376
|
+
# 如果没有分隔符,尝试单个键值对
|
275
377
|
if '=' in value:
|
276
|
-
|
277
|
-
return {
|
378
|
+
key, val = value.split('=', 1)
|
379
|
+
return {key.strip(): val.strip()}
|
278
380
|
return {}
|
279
381
|
elif target_type == int:
|
382
|
+
# 支持十六进制、八进制、二进制
|
280
383
|
value = value.strip().lower()
|
281
384
|
if value.startswith('0x'):
|
282
385
|
return int(value, 16)
|
@@ -293,7 +396,12 @@ class ConfigParser:
|
|
293
396
|
return value.encode('utf-8')
|
294
397
|
elif target_type == bytearray:
|
295
398
|
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(','))
|
296
403
|
elif target_type == range:
|
404
|
+
# 支持 range 类型,格式:start:stop:step 或 start:stop
|
297
405
|
parts = value.split(':')
|
298
406
|
if len(parts) == 2:
|
299
407
|
return range(int(parts[0]), int(parts[1]))
|
@@ -304,41 +412,79 @@ class ConfigParser:
|
|
304
412
|
except (ValueError, TypeError) as e:
|
305
413
|
raise ConfigValueError(
|
306
414
|
f"无法将值 '{value}' 转换为类型 {target_type.__name__}",
|
307
|
-
file_path=
|
308
|
-
key=
|
415
|
+
file_path=None,
|
416
|
+
key=None
|
309
417
|
)
|
310
418
|
|
311
419
|
def get_value(self, file_path: Optional[Union[str, Path]] = None, key: str = None,
|
312
420
|
section: Optional[str] = None, default: Any = None,
|
313
421
|
value_type: Optional[Type[T]] = None) -> T:
|
314
|
-
"""获取指定配置项的值
|
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
|
+
"""
|
315
439
|
if file_path is None:
|
316
440
|
self._ensure_file_open()
|
317
441
|
file_path = self._current_file
|
318
442
|
if not self._validate_key(key):
|
319
443
|
raise ConfigValueError(f"无效的键名: {key}", file_path=file_path, key=key)
|
444
|
+
|
320
445
|
config = self.read(file_path)
|
321
446
|
section = section or self.options.default_section
|
322
447
|
normalized_section = self._normalize_section(section)
|
448
|
+
|
449
|
+
# 获取原始节名称
|
323
450
|
original_section = self._get_original_section(str(file_path), normalized_section)
|
324
451
|
if original_section is None:
|
325
452
|
if default is not None:
|
326
453
|
return default
|
327
454
|
raise ConfigSectionNotFoundError(file_path, section)
|
455
|
+
|
328
456
|
if key not in config[original_section]:
|
329
457
|
if default is not None:
|
330
458
|
return default
|
331
459
|
raise ConfigKeyNotFoundError(file_path, original_section, key)
|
460
|
+
|
332
461
|
value = config[original_section][key]
|
462
|
+
|
333
463
|
if value_type is not None:
|
334
|
-
return self._convert_value(value, value_type
|
464
|
+
return self._convert_value(value, value_type)
|
465
|
+
|
335
466
|
return value
|
336
467
|
|
337
468
|
def get_values(self, keys: List[Tuple[str, str]],
|
338
469
|
file_path: Optional[Union[str, Path]] = None,
|
339
470
|
defaults: Optional[Dict[str, Any]] = None,
|
340
471
|
value_types: Optional[Dict[str, Type]] = None) -> Dict[str, Any]:
|
341
|
-
"""批量获取多个配置项的值
|
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
|
+
"""
|
342
488
|
if file_path is None:
|
343
489
|
self._ensure_file_open()
|
344
490
|
file_path = self._current_file
|
@@ -369,7 +515,23 @@ class ConfigParser:
|
|
369
515
|
file_path: Optional[Union[str, Path]] = None,
|
370
516
|
defaults: Optional[Dict[str, Any]] = None,
|
371
517
|
value_types: Optional[Dict[str, Type]] = None) -> Tuple[Any, ...]:
|
372
|
-
"""获取指定节点下多个键的值元组
|
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
|
+
"""
|
373
535
|
if file_path is None:
|
374
536
|
self._ensure_file_open()
|
375
537
|
file_path = self._current_file
|
@@ -399,20 +561,37 @@ class ConfigParser:
|
|
399
561
|
section: Optional[str] = None,
|
400
562
|
file_path: Optional[Union[str, Path]] = None,
|
401
563
|
value_type: Optional[Type] = None) -> None:
|
402
|
-
"""
|
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
|
+
"""
|
403
577
|
if file_path is None:
|
404
578
|
self._ensure_file_open()
|
405
579
|
file_path = self._current_file
|
406
580
|
if not self._validate_key(key):
|
407
581
|
raise ConfigValueError(f"无效的键名: {key}", file_path=file_path, key=key)
|
408
|
-
|
582
|
+
|
583
|
+
# 读取原始文件內容
|
409
584
|
original_lines = []
|
410
585
|
if file_path.exists():
|
411
586
|
with open(file_path, 'r', encoding=self.options.encoding) as file:
|
412
587
|
original_lines = file.readlines()
|
588
|
+
|
589
|
+
# 读取当前配置
|
413
590
|
config = self.read(file_path)
|
591
|
+
|
414
592
|
if section not in config:
|
415
593
|
config[section] = {}
|
594
|
+
|
416
595
|
if value_type is not None:
|
417
596
|
try:
|
418
597
|
if value_type == bool:
|
@@ -429,63 +608,96 @@ class ConfigParser:
|
|
429
608
|
section=section,
|
430
609
|
key=key
|
431
610
|
)
|
611
|
+
|
432
612
|
if isinstance(value, bool):
|
433
613
|
value = str(value).lower()
|
434
614
|
else:
|
435
615
|
value = str(value)
|
616
|
+
|
617
|
+
# 更新配置
|
436
618
|
config[section][key] = value
|
619
|
+
|
620
|
+
# 写入文件,保持原始格式
|
437
621
|
try:
|
438
622
|
file_path.parent.mkdir(parents=True, exist_ok=True)
|
623
|
+
|
439
624
|
with open(file_path, 'w', encoding=self.options.encoding) as file:
|
440
625
|
current_section = self.options.default_section
|
441
|
-
section_separators = {}
|
626
|
+
section_separators = {} # 用于存储每个section使用的分隔符
|
627
|
+
|
628
|
+
# 解析原始文件,提取格式信息
|
442
629
|
for line in original_lines:
|
443
630
|
stripped_line = line.strip()
|
631
|
+
|
444
632
|
if not stripped_line:
|
445
|
-
file.write(line)
|
633
|
+
file.write(line) # 保持空行
|
446
634
|
continue
|
635
|
+
|
447
636
|
if stripped_line.startswith('[') and stripped_line.endswith(']'):
|
448
637
|
current_section = stripped_line[1:-1]
|
449
|
-
file.write(line)
|
638
|
+
file.write(line) # 保持节标记的原始格式
|
450
639
|
continue
|
640
|
+
|
451
641
|
if self._is_comment_line(stripped_line):
|
452
|
-
file.write(line)
|
642
|
+
file.write(line) # 保持注释的原始格式
|
453
643
|
continue
|
644
|
+
|
454
645
|
key_value = self._split_key_value(stripped_line)
|
455
646
|
if key_value:
|
456
647
|
orig_key, orig_value = key_value
|
648
|
+
# 检测使用的分隔符
|
457
649
|
for sep in self.options.separators:
|
458
650
|
if sep in line:
|
459
651
|
section_separators.setdefault(current_section, {})[orig_key] = sep
|
460
652
|
break
|
653
|
+
|
654
|
+
# 如果是当前要修改的键,则写入新值
|
461
655
|
if current_section == section and orig_key == key:
|
462
656
|
separator = section_separators.get(current_section, {}).get(orig_key, self.options.separators[0])
|
657
|
+
# 提取行尾注释
|
463
658
|
comment = ''
|
464
659
|
for style in self.options.comment_styles:
|
465
660
|
comment_match = re.search(fr'\s+{re.escape(style.value)}.*$', line)
|
466
661
|
if comment_match:
|
467
662
|
comment = comment_match.group(0)
|
468
663
|
break
|
664
|
+
# 写入新值并保留注释
|
469
665
|
file.write(f'{key}{separator}{value}{comment}\n')
|
470
666
|
else:
|
471
|
-
file.write(line)
|
667
|
+
file.write(line) # 保持其他行的原始格式
|
472
668
|
else:
|
473
|
-
file.write(line)
|
669
|
+
file.write(line) # 保持无法解析的行的原始格式
|
670
|
+
|
671
|
+
# 如果section不存在,则添加新的section
|
474
672
|
if section not in [line.strip()[1:-1] for line in original_lines if line.strip().startswith('[') and line.strip().endswith(']')]:
|
475
673
|
file.write(f'\n[{section}]\n')
|
476
674
|
file.write(f'{key}={value}\n')
|
675
|
+
|
477
676
|
self._clear_cache(str(file_path))
|
677
|
+
|
478
678
|
except Exception as e:
|
479
679
|
raise ConfigWriteError(file_path, e)
|
480
680
|
|
481
681
|
def read(self, file_path: Optional[Union[str, Path]] = None) -> Dict[str, Any]:
|
482
|
-
"""读取配置文件内容
|
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
|
+
"""
|
483
694
|
if file_path is None:
|
484
695
|
self._ensure_file_open()
|
485
696
|
file_path = self._current_file
|
486
697
|
else:
|
487
|
-
file_path = Path(file_path)
|
698
|
+
file_path = Path(file_path) # 确保 file_path 是 Path 对象
|
488
699
|
|
700
|
+
# 检查缓存
|
489
701
|
cached_config = self._get_cached_config(str(file_path))
|
490
702
|
if cached_config is not None:
|
491
703
|
return cached_config
|
@@ -555,39 +767,49 @@ class ConfigParser:
|
|
555
767
|
|
556
768
|
except Exception as e:
|
557
769
|
raise ConfigReadError(file_path, e)
|
558
|
-
|
770
|
+
|
559
771
|
|
560
772
|
def main() -> None:
|
561
|
-
|
773
|
+
# 使用示例
|
562
774
|
config_file = Path('/Users/xigua/spd.txt')
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
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(
|
775
|
+
|
776
|
+
# 方式1:使用上下文管理器
|
777
|
+
with ConfigParser() as parser:
|
778
|
+
parser.open(config_file)
|
779
|
+
host, port, username, password = parser.get_section_values(
|
577
780
|
keys=['host', 'port', 'username', 'password'],
|
578
781
|
section='mysql'
|
579
782
|
)
|
580
|
-
print("
|
581
|
-
|
582
|
-
|
583
|
-
host,
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
)
|
588
|
-
print("\n
|
589
|
-
|
590
|
-
print(f"
|
783
|
+
print("方式1结果:", host, port, username, password)
|
784
|
+
|
785
|
+
# 修改配置
|
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
|
+
|
797
|
+
# 方式2:链式调用
|
798
|
+
parser = ConfigParser()
|
799
|
+
host, port, username, password = parser.open(config_file).get_section_values(
|
800
|
+
keys=['host', 'port', 'username', 'password'],
|
801
|
+
section='mysql'
|
802
|
+
)
|
803
|
+
print("\n方式2结果:", host, port, username, password)
|
804
|
+
|
805
|
+
# 方式3:传统方式
|
806
|
+
parser = ConfigParser()
|
807
|
+
host, port, username, password = parser.get_section_values(
|
808
|
+
file_path=config_file,
|
809
|
+
section='mysql',
|
810
|
+
keys=['host', 'port', 'username', 'password']
|
811
|
+
)
|
812
|
+
print("\n方式3结果:", host, port, username, password)
|
591
813
|
|
592
814
|
|
593
815
|
if __name__ == '__main__':
|