tretool 0.2.1__py3-none-any.whl → 1.0.0__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.
tretool/config.py CHANGED
@@ -1,262 +1,498 @@
1
- """
2
- ### 配置管理库,提供安全的键值对存储和访问机制
3
-
4
- 特性:
5
- - 类型安全的设置和获取
6
- - 默认值支持
7
- - 批量操作支持
8
- - 配置项存在性检查
9
- - 防止意外覆盖
10
- - 配置文件持久化
11
- - 配置变更回调
12
- """
13
-
14
1
  import json
15
2
  import os
16
- from typing import Any, Callable, Dict, Optional
3
+ import threading
4
+ from typing import Any, Callable, Dict, Optional, Union, List, TypeVar, Generic
5
+ from dataclasses import dataclass
6
+ from enum import Enum, auto
7
+ from pathlib import Path
8
+ import logging
9
+ from copy import deepcopy
10
+
11
+ # 配置日志
12
+ logging.basicConfig(level=logging.INFO)
13
+ logger = logging.getLogger(__name__)
14
+
15
+ T = TypeVar('T')
16
+
17
+ class ConfigError(Exception):
18
+ """配置基础异常"""
19
+ pass
20
+
21
+ class ConfigLockedError(ConfigError):
22
+ """配置被锁定异常"""
23
+ pass
24
+
25
+ class ConfigValidationError(ConfigError):
26
+ """配置验证失败异常"""
27
+ pass
28
+
29
+ class ConfigOperation(Enum):
30
+ """配置操作类型"""
31
+ SET = auto()
32
+ DELETE = auto()
33
+ RESET = auto()
34
+
35
+ @dataclass
36
+ class ConfigChangeEvent:
37
+ """配置变更事件"""
38
+ key: str
39
+ old_value: Any
40
+ new_value: Any
41
+ operation: ConfigOperation
42
+
43
+ class ConfigValidator(Generic[T]):
44
+ """配置验证器基类"""
45
+ def validate(self, value: Any) -> T:
46
+ """验证并转换配置值"""
47
+ raise NotImplementedError
48
+
49
+ class IntValidator(ConfigValidator[int]):
50
+ """整数验证器"""
51
+ def __init__(self, min_value: Optional[int] = None, max_value: Optional[int] = None):
52
+ self.min = min_value
53
+ self.max = max_value
54
+
55
+ def validate(self, value: Any) -> int:
56
+ try:
57
+ val = int(value)
58
+ if self.min is not None and val < self.min:
59
+ raise ConfigValidationError(f"值 {val} 小于最小值 {self.min}")
60
+ if self.max is not None and val > self.max:
61
+ raise ConfigValidationError(f"值 {val} 大于最大值 {self.max}")
62
+ return val
63
+ except (ValueError, TypeError) as e:
64
+ raise ConfigValidationError(f"无效的整数值: {value}") from e
17
65
 
18
66
  class Config:
19
67
  """
20
- 配置管理类,提供安全的键值对存储和访问机制
21
-
22
- 特性:
23
- - 类型安全的设置和获取
24
- - 默认值支持
25
- - 批量操作支持
26
- - 配置项存在性检查
27
- - 防止意外覆盖
28
- - 配置变更通知
68
+ 增强版配置管理类,提供类型安全、线程安全的配置管理
29
69
 
30
- 使用示例:
31
- ```
32
- config = Config({"theme": "dark"})
33
- config.set_config("font_size", 14)
34
- theme = config.get_config("theme", default="light")
35
-
36
- # 添加配置变更监听
37
- def on_config_change(key, old_value, new_value):
38
- print(f"配置变更: {key} 从 {old_value} 改为 {new_value}")
39
-
40
- config.add_change_listener(on_config_change)
41
-
42
- # 保存和加载配置
43
- config.save_to_file("settings.json")
44
- new_config = Config.load_from_file("settings.json")
45
- ```
70
+ 主要特性:
71
+ - 类型安全的配置存取
72
+ - 配置变更监听和通知
73
+ - 配置验证和转换
74
+ - 多格式持久化支持
75
+ - 原子操作和事务支持
76
+ - 配置版本控制
77
+ - 环境变量集成
46
78
  """
47
79
 
48
- def __init__(self, initial_config: Optional[Dict[str, Any]] = None):
80
+ def __init__(
81
+ self,
82
+ initial_config: Optional[Dict[str, Any]] = None,
83
+ validators: Optional[Dict[str, ConfigValidator]] = None,
84
+ config_dir: Optional[Union[str, Path]] = None,
85
+ env_prefix: Optional[str] = None
86
+ ):
49
87
  """
50
88
  初始化配置存储
51
89
 
52
90
  参数:
53
- initial_config: 初始配置字典 (可选)
91
+ initial_config: 初始配置字典
92
+ validators: 配置验证器字典 {key: validator}
93
+ config_dir: 配置文件存储目录
94
+ env_prefix: 环境变量前缀
54
95
  """
55
- self.config_dict = initial_config.copy() if initial_config else {}
56
- self._lock = False # 防止意外修改的锁
57
- self._change_listeners = [] # 配置变更监听器列表
96
+ self._data = deepcopy(initial_config) if initial_config else {}
97
+ self._validators = validators or {}
98
+ self._lock = threading.Lock()
99
+ self._change_listeners = []
100
+ self._config_dir = Path(config_dir) if config_dir else None
101
+ self._env_prefix = f"{env_prefix}_" if env_prefix else ""
102
+ self._version = 1
103
+ self._transaction_stack = []
58
104
 
105
+ # 从环境变量加载配置
106
+ self._load_from_env()
107
+
59
108
  def __str__(self) -> str:
60
109
  """返回配置的可读字符串表示"""
61
- return json.dumps(self.config_dict, indent=2, ensure_ascii=False)
110
+ return json.dumps(self._data, indent=2, ensure_ascii=False)
62
111
 
63
112
  def __repr__(self) -> str:
64
113
  """返回配置的正式表示"""
65
- return f"Config({self.config_dict})"
114
+ return f"Config({self._data})"
66
115
 
67
- def __contains__(self, item: str) -> bool:
116
+ def __contains__(self, key: str) -> bool:
68
117
  """检查配置项是否存在"""
69
- return item in self.config_dict
118
+ return key in self._data
70
119
 
71
120
  def __len__(self) -> int:
72
121
  """返回配置项的数量"""
73
- return len(self.config_dict)
122
+ return len(self._data)
123
+
124
+ def __enter__(self):
125
+ """进入事务上下文"""
126
+ self.begin_transaction()
127
+ return self
128
+
129
+ def __exit__(self, exc_type, exc_val, exc_tb):
130
+ """退出事务上下文"""
131
+ if exc_type is None:
132
+ self.commit_transaction()
133
+ else:
134
+ self.rollback_transaction()
135
+
136
+ @property
137
+ def version(self) -> int:
138
+ """获取配置版本"""
139
+ return self._version
74
140
 
75
- def add_change_listener(self, listener: Callable[[str, Any, Any], None]):
141
+ def begin_transaction(self):
142
+ """开始一个配置事务"""
143
+ with self._lock:
144
+ self._transaction_stack.append(deepcopy(self._data))
145
+
146
+ def commit_transaction(self):
147
+ """提交当前事务"""
148
+ with self._lock:
149
+ if not self._transaction_stack:
150
+ raise ConfigError("没有活跃的事务可提交")
151
+ self._transaction_stack.pop()
152
+ self._version += 1
153
+
154
+ def rollback_transaction(self):
155
+ """回滚当前事务"""
156
+ with self._lock:
157
+ if not self._transaction_stack:
158
+ raise ConfigError("没有活跃的事务可回滚")
159
+ self._data = self._transaction_stack.pop()
160
+
161
+ def add_change_listener(self, listener: Callable[[ConfigChangeEvent], None]):
76
162
  """
77
163
  添加配置变更监听器
78
164
 
79
165
  参数:
80
- listener: 回调函数,格式为 func(key, old_value, new_value)
166
+ listener: 回调函数,接收 ConfigChangeEvent 参数
81
167
  """
82
- if listener not in self._change_listeners:
83
- self._change_listeners.append(listener)
168
+ with self._lock:
169
+ if listener not in self._change_listeners:
170
+ self._change_listeners.append(listener)
84
171
 
85
- def remove_change_listener(self, listener: Callable[[str, Any, Any], None]):
172
+ def remove_change_listener(self, listener: Callable[[ConfigChangeEvent], None]):
86
173
  """移除配置变更监听器"""
87
- if listener in self._change_listeners:
88
- self._change_listeners.remove(listener)
174
+ with self._lock:
175
+ if listener in self._change_listeners:
176
+ self._change_listeners.remove(listener)
89
177
 
90
- def _notify_change(self, key: str, old_value: Any, new_value: Any):
178
+ def _notify_change(self, event: ConfigChangeEvent):
91
179
  """通知所有监听器配置变更"""
92
- for listener in self._change_listeners:
180
+ with self._lock:
181
+ listeners = self._change_listeners.copy()
182
+
183
+ for listener in listeners:
93
184
  try:
94
- listener(key, old_value, new_value)
185
+ listener(event)
95
186
  except Exception as e:
96
- print(f"配置变更通知错误: {e}")
187
+ logger.error(f"配置变更通知错误: {e}", exc_info=True)
97
188
 
98
- def get_config(self, item: str, default: Any = None) -> Any:
189
+ def get(self, key: str, default: Any = None, validate: bool = True) -> Any:
99
190
  """
100
- 安全获取配置项
191
+ 获取配置项
101
192
 
102
193
  参数:
103
- item: 配置键名
104
- default: 当键不存在时返回的默认值
194
+ key: 配置键名
195
+ default: 默认值
196
+ validate: 是否验证返回值
105
197
 
106
198
  返回:
107
199
  配置值或默认值
200
+
201
+ 异常:
202
+ ConfigValidationError: 验证失败
108
203
  """
109
- return self.config_dict.get(item, default)
110
-
111
- def set_config(self, item: str, value: Any) -> bool:
204
+ with self._lock:
205
+ value = self._data.get(key, default)
206
+
207
+ if validate and key in self._validators:
208
+ try:
209
+ return self._validators[key].validate(value)
210
+ except ConfigValidationError as e:
211
+ logger.warning(f"配置验证失败 [{key}]: {e}")
212
+ raise
213
+
214
+ return value
215
+
216
+ def set(self, key: str, value: Any, validate: bool = True) -> bool:
112
217
  """
113
218
  设置配置项
114
219
 
115
220
  参数:
116
- item: 配置键名
221
+ key: 配置键名
117
222
  value: 配置值
223
+ validate: 是否验证值
118
224
 
119
225
  返回:
120
226
  True 设置成功, False 设置失败
121
- """
122
- if self._lock:
123
- print(f"警告: 配置系统已锁定,无法修改 '{item}'")
124
- return False
125
227
 
126
- old_value = self.config_dict.get(item)
127
- self.config_dict[item] = value
228
+ 异常:
229
+ ConfigValidationError: 验证失败
230
+ ConfigLockedError: 配置被锁定
231
+ """
232
+ if validate and key in self._validators:
233
+ value = self._validators[key].validate(value)
128
234
 
129
- # 通知变更
130
- self._notify_change(item, old_value, value)
131
- return True
132
-
133
- def delete_config(self, item: str) -> bool:
235
+ with self._lock:
236
+ if not self._transaction_stack:
237
+ raise ConfigError("必须在事务中修改配置")
238
+
239
+ old_value = self._data.get(key)
240
+ self._data[key] = value
241
+
242
+ # 通知变更
243
+ event = ConfigChangeEvent(
244
+ key=key,
245
+ old_value=old_value,
246
+ new_value=value,
247
+ operation=ConfigOperation.SET
248
+ )
249
+ self._notify_change(event)
250
+
251
+ return True
252
+
253
+ def delete(self, key: str) -> bool:
134
254
  """
135
255
  删除配置项
136
256
 
137
257
  参数:
138
- item: 要删除的配置键名
258
+ key: 要删除的配置键名
139
259
 
140
260
  返回:
141
261
  True 删除成功, False 键不存在
262
+
263
+ 异常:
264
+ ConfigLockedError: 配置被锁定
142
265
  """
143
- if item in self.config_dict:
144
- old_value = self.config_dict[item]
145
- del self.config_dict[item]
266
+ with self._lock:
267
+ if not self._transaction_stack:
268
+ raise ConfigError("必须在事务中修改配置")
269
+
270
+ if key not in self._data:
271
+ return False
272
+
273
+ old_value = self._data[key]
274
+ del self._data[key]
275
+
276
+ # 通知变更
277
+ event = ConfigChangeEvent(
278
+ key=key,
279
+ old_value=old_value,
280
+ new_value=None,
281
+ operation=ConfigOperation.DELETE
282
+ )
283
+ self._notify_change(event)
146
284
 
147
- # 通知变更 (值为 None 表示删除)
148
- self._notify_change(item, old_value, None)
149
285
  return True
150
- return False
151
-
152
- def save_to_file(self, filename: str) -> bool:
286
+
287
+ def has(self, key: str) -> bool:
288
+ """检查配置项是否存在"""
289
+ with self._lock:
290
+ return key in self._data
291
+
292
+ def bulk_update(self, updates: Dict[str, Any], validate: bool = True) -> bool:
153
293
  """
154
- 保存配置到文件
294
+ 批量更新配置
155
295
 
156
296
  参数:
157
- filename: 文件名
297
+ updates: 包含多个键值对的字典
298
+ validate: 是否验证值
158
299
 
159
300
  返回:
160
- True 保存成功, False 保存失败
301
+ True 更新成功
302
+
303
+ 异常:
304
+ ConfigValidationError: 验证失败
305
+ ConfigLockedError: 配置被锁定
161
306
  """
162
- try:
163
- with open(filename, 'w', encoding='utf-8') as f:
164
- json.dump(self.get_all_configs(), f, indent=2, ensure_ascii=False)
307
+ if validate:
308
+ for key, value in updates.items():
309
+ if key in self._validators:
310
+ updates[key] = self._validators[key].validate(value)
311
+
312
+ with self._lock:
313
+ if not self._transaction_stack:
314
+ raise ConfigError("必须在事务中修改配置")
315
+
316
+ # 记录变更
317
+ changes = []
318
+ for key, value in updates.items():
319
+ old_value = self._data.get(key)
320
+ self._data[key] = value
321
+ changes.append(ConfigChangeEvent(
322
+ key=key,
323
+ old_value=old_value,
324
+ new_value=value,
325
+ operation=ConfigOperation.SET
326
+ ))
327
+
328
+ # 批量通知变更
329
+ for event in changes:
330
+ self._notify_change(event)
331
+
165
332
  return True
166
- except (IOError, TypeError) as e:
167
- print(f"保存配置失败: {e}")
168
- return False
169
-
170
- @classmethod
171
- def load_from_file(cls, filename: str) -> Optional['Config']:
333
+
334
+ def reset(self, new_config: Optional[Dict[str, Any]] = None) -> None:
172
335
  """
173
- 从文件加载配置
336
+ 重置所有配置
174
337
 
175
338
  参数:
176
- filename: 文件名
339
+ new_config: 新的配置字典 (可选,默认清空)
177
340
 
178
- 返回:
179
- 加载成功的 Config 实例,失败返回 None
341
+ 异常:
342
+ ConfigLockedError: 配置被锁定
180
343
  """
181
- if not os.path.exists(filename):
182
- print(f"配置文件不存在: {filename}")
183
- return None
344
+ with self._lock:
345
+ if not self._transaction_stack:
346
+ raise ConfigError("必须在事务中修改配置")
184
347
 
185
- try:
186
- with open(filename, 'r', encoding='utf-8') as f:
187
- config_data = json.load(f)
188
- return cls(config_data)
189
- except (IOError, json.JSONDecodeError) as e:
190
- print(f"加载配置失败: {e}")
191
- return None
192
-
193
- def has_config(self, item: str) -> bool:
194
- """检查配置项是否存在"""
195
- return item in self.config_dict
196
-
197
- def lock_config(self):
198
- """锁定配置防止修改"""
199
- self._lock = True
200
-
201
- def unlock_config(self):
202
- """解锁配置允许修改"""
203
- self._lock = False
204
-
205
- def is_locked(self) -> bool:
206
- """检查配置是否已锁定"""
207
- return self._lock
208
-
209
- def bulk_update(self, update_dict: Dict[str, Any]) -> bool:
348
+ # 记录所有变更(删除)
349
+ delete_events = []
350
+ for key in list(self._data.keys()):
351
+ delete_events.append(ConfigChangeEvent(
352
+ key=key,
353
+ old_value=self._data[key],
354
+ new_value=None,
355
+ operation=ConfigOperation.DELETE
356
+ ))
357
+
358
+ # 重置配置
359
+ new_data = deepcopy(new_config) if new_config else {}
360
+ self._data = new_data
361
+
362
+ # 通知所有删除和新配置项
363
+ for event in delete_events:
364
+ self._notify_change(event)
365
+
366
+ for key, value in self._data.items():
367
+ self._notify_change(ConfigChangeEvent(
368
+ key=key,
369
+ old_value=None,
370
+ new_value=value,
371
+ operation=ConfigOperation.SET
372
+ ))
373
+
374
+ def to_dict(self) -> Dict[str, Any]:
375
+ """获取所有配置的深拷贝"""
376
+ with self._lock:
377
+ return deepcopy(self._data)
378
+
379
+ def save(self, filename: Optional[str] = None, format: str = 'json') -> bool:
210
380
  """
211
- 批量更新配置
381
+ 保存配置到文件
212
382
 
213
383
  参数:
214
- update_dict: 包含多个键值对的字典
384
+ filename: 文件名 (可选,使用默认配置目录)
385
+ format: 文件格式 ('json', 'yaml')
215
386
 
216
387
  返回:
217
- True 更新成功, False 更新失败
388
+ True 保存成功, False 保存失败
218
389
  """
219
- if self._lock:
220
- print("警告: 配置系统已锁定,批量更新被拒绝")
221
- return False
222
-
223
- # 记录变更
224
- changes = {}
225
- for key, value in update_dict.items():
226
- old_value = self.config_dict.get(key)
227
- self.config_dict[key] = value
228
- changes[key] = (old_value, value)
390
+ if filename is None and self._config_dir is None:
391
+ raise ConfigError("未指定文件名且未设置配置目录")
229
392
 
230
- # 批量通知变更
231
- for key, (old_value, new_value) in changes.items():
232
- self._notify_change(key, old_value, new_value)
233
-
234
- return True
235
-
236
- def get_all_configs(self) -> Dict[str, Any]:
237
- """获取所有配置的副本"""
238
- return self.config_dict.copy()
393
+ filepath = Path(filename) if filename else self._config_dir / f"config.{format}"
394
+ filepath.parent.mkdir(parents=True, exist_ok=True)
395
+
396
+ try:
397
+ with filepath.open('w', encoding='utf-8') as f:
398
+ if format == 'json':
399
+ json.dump(self.to_dict(), f, indent=2, ensure_ascii=False)
400
+ elif format == 'yaml':
401
+ import yaml
402
+ yaml.safe_dump(self.to_dict(), f, allow_unicode=True)
403
+ else:
404
+ raise ConfigError(f"不支持的格式: {format}")
405
+ return True
406
+ except Exception as e:
407
+ logger.error(f"保存配置失败: {e}", exc_info=True)
408
+ return False
239
409
 
240
- def reset_config(self, new_config: Optional[Dict[str, Any]] = None) -> None:
410
+ @classmethod
411
+ def load(
412
+ cls,
413
+ filename: str,
414
+ validators: Optional[Dict[str, ConfigValidator]] = None,
415
+ config_dir: Optional[Union[str, Path]] = None,
416
+ env_prefix: Optional[str] = None
417
+ ) -> 'Config':
241
418
  """
242
- 重置所有配置
419
+ 从文件加载配置
243
420
 
244
421
  参数:
245
- new_config: 新的配置字典 (可选,默认清空)
246
- """
247
- if self._lock:
248
- print("警告: 配置系统已锁定,无法重置")
249
- return
422
+ filename: 文件名
423
+ validators: 配置验证器
424
+ config_dir: 配置目录
425
+ env_prefix: 环境变量前缀
250
426
 
251
- # 记录所有变更(删除)
252
- for key in list(self.config_dict.keys()):
253
- old_value = self.config_dict[key]
254
- self._notify_change(key, old_value, None)
427
+ 返回:
428
+ 加载的 Config 实例
429
+
430
+ 异常:
431
+ ConfigError: 加载失败
432
+ """
433
+ filepath = Path(filename)
434
+ if not filepath.is_absolute() and config_dir is not None:
435
+ filepath = Path(config_dir) / filename
255
436
 
256
- # 重置配置
257
- self.config_dict = new_config.copy() if new_config else {}
437
+ try:
438
+ with filepath.open('r', encoding='utf-8') as f:
439
+ if filepath.suffix.lower() == '.json':
440
+ data = json.load(f)
441
+ elif filepath.suffix.lower() in ('.yaml', '.yml'):
442
+ import yaml
443
+ data = yaml.safe_load(f)
444
+ else:
445
+ raise ConfigError(f"不支持的文件格式: {filepath.suffix}")
446
+
447
+ return cls(
448
+ initial_config=data,
449
+ validators=validators,
450
+ config_dir=config_dir,
451
+ env_prefix=env_prefix
452
+ )
453
+ except Exception as e:
454
+ raise ConfigError(f"加载配置失败: {e}") from e
455
+
456
+ def _load_from_env(self):
457
+ """从环境变量加载配置"""
458
+ if not self._env_prefix:
459
+ return
258
460
 
259
- # 通知所有新配置项
260
- for key, value in self.config_dict.items():
261
- self._notify_change(key, None, value)
461
+ for key, value in os.environ.items():
462
+ if key.startswith(self._env_prefix):
463
+ config_key = key[len(self._env_prefix):].lower()
464
+ try:
465
+ # 尝试解析JSON格式的环境变量
466
+ parsed_value = json.loads(value)
467
+ self._data[config_key] = parsed_value
468
+ except json.JSONDecodeError:
469
+ # 普通字符串值
470
+ self._data[config_key] = value
262
471
 
472
+ def register_validator(self, key: str, validator: ConfigValidator):
473
+ """注册配置验证器"""
474
+ with self._lock:
475
+ self._validators[key] = validator
476
+
477
+ def unregister_validator(self, key: str):
478
+ """移除配置验证器"""
479
+ with self._lock:
480
+ if key in self._validators:
481
+ del self._validators[key]
482
+
483
+ def get_validator(self, key: str) -> Optional[ConfigValidator]:
484
+ """获取配置验证器"""
485
+ with self._lock:
486
+ return self._validators.get(key)
487
+
488
+ def validate_all(self) -> Dict[str, Union[Any, Exception]]:
489
+ """验证所有配置项,返回验证结果字典"""
490
+ results = {}
491
+ with self._lock:
492
+ for key, validator in self._validators.items():
493
+ if key in self._data:
494
+ try:
495
+ results[key] = validator.validate(self._data[key])
496
+ except Exception as e:
497
+ results[key] = e
498
+ return results