xiaoe-config-manager 1.0.0__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.
@@ -0,0 +1,256 @@
1
+ Metadata-Version: 2.4
2
+ Name: xiaoe-config-manager
3
+ Version: 1.0.0
4
+ Summary: 多进程安全的 JSON 配置文件管理器,支持默认配置、动态重置与进程间自动同步
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://github.com/xiaoe/xiaoe-config-manager
7
+ Project-URL: Repository, https://github.com/xiaoe/xiaoe-config-manager
8
+ Keywords: config,configuration,json,multiprocess,thread-safe
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.8
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Requires-Python: >=3.8
21
+ Description-Content-Type: text/markdown
22
+
23
+ # xiaoe-config-manager
24
+
25
+ 多进程安全的 JSON 配置文件管理器,纯 Python 标准库实现,零外部依赖。
26
+
27
+ ## 特性
28
+
29
+ - **线程安全** — 内置线程锁,单进程多线程环境下安全读写
30
+ - **多进程安全** — 通过 `multiprocessing.Queue` 双向管道实现主进程与子进程之间的配置自动同步
31
+ - **默认配置** — 支持静态默认字典和动态回调两种方式,配置项缺失时自动补齐
32
+ - **配置重置** — 支持单个 key 或全部配置一键恢复默认值
33
+ - **默认值热更新** — 当默认配置变化时,自动识别用户未修改的项并更新为新默认值
34
+ - **纯标准库** — 无任何第三方依赖,仅使用 `json`、`threading`、`multiprocessing` 等标准库
35
+
36
+ ## 安装
37
+
38
+ ```bash
39
+ pip install xiaoe-config-manager
40
+ ```
41
+
42
+ ## 快速开始
43
+
44
+ ### 单进程使用
45
+
46
+ ```python
47
+ from config import Config
48
+
49
+ # 初始化,指定配置文件名(不含扩展名)
50
+ config = Config("app_config")
51
+
52
+ # 写入配置
53
+ config.configset("api_url", "https://api.example.com")
54
+ config.configset("timeout", 30)
55
+
56
+ # 读取配置
57
+ print(config.configget("api_url")) # https://api.example.com
58
+
59
+ # 读取全部配置
60
+ all_config = config.configget("all")
61
+ print(all_config) # {"api_url": "https://api.example.com", "timeout": 30}
62
+ ```
63
+
64
+ ### 带默认配置
65
+
66
+ ```python
67
+ from config import Config
68
+
69
+ defaults = {
70
+ "host": "0.0.0.0",
71
+ "port": 8080,
72
+ "debug": False,
73
+ }
74
+
75
+ config = Config("server_config", default_config=defaults)
76
+
77
+ # 配置文件中已有的键保留原值,缺失的键自动用默认值补齐
78
+ print(config.configget("host")) # "0.0.0.0"
79
+ print(config.configget("port")) # 8080
80
+
81
+ # 恢复单个配置项为默认值
82
+ config.configreset("port")
83
+
84
+ # 恢复全部配置为默认值
85
+ config.configreset("all")
86
+ ```
87
+
88
+ ### 使用回调函数动态生成默认配置
89
+
90
+ ```python
91
+ from config import Config
92
+
93
+ def dynamic_defaults(current_config):
94
+ """根据当前配置动态生成默认值"""
95
+ import socket
96
+ return {
97
+ "host": "0.0.0.0",
98
+ "port": 8080,
99
+ "hostname": socket.gethostname(),
100
+ "worker_count": 4,
101
+ }
102
+
103
+ config = Config(
104
+ "dynamic_config",
105
+ default_config_callback=dynamic_defaults,
106
+ init_use_callback=True, # 初始化时调用回调
107
+ )
108
+
109
+ # 重置时会再次调用回调,获取最新的默认值
110
+ config.configreset("all")
111
+ ```
112
+
113
+ ## 多进程使用
114
+
115
+ 多进程场景下,**主进程持有 `Config` 单例**,子进程通过 `ConfigInChildProcessPipe` 与主进程通信。主进程需要为每个子进程创建一个 `ConfigInMainProcessPipe` 实例。
116
+
117
+ ### 主进程
118
+
119
+ ```python
120
+ from config import Config, ConfigInMainProcessPipe
121
+
122
+ # 1. 创建配置单例
123
+ config = Config("my_config", default_config={"workers": 4})
124
+
125
+ # 2. 为子进程创建主进程端的管道
126
+ main_pipe = ConfigInMainProcessPipe(config)
127
+
128
+ # 3. 获取队列,传递给子进程
129
+ set_queue, get_queue = main_pipe.get_queues()
130
+
131
+ # 4. 启动子进程(以 multiprocessing.Process 为例)
132
+ import multiprocessing
133
+
134
+ def worker(set_q, get_q):
135
+ from config import ConfigInChildProcessPipe
136
+ # 5. 为子进程创建子进程端的管道
137
+ child_config = ConfigInChildProcessPipe(set_q, get_q)
138
+ child_config.configset("worker_status", "running")
139
+ child_config.close()
140
+
141
+ p = multiprocessing.Process(target=worker, args=(set_queue, get_queue))
142
+ p.start()
143
+ p.join()
144
+
145
+ # 6. 主进程可直接读写配置,修改会自动同步并广播到到子进程。
146
+ # 同样,子进程的修改请求会提交至主进程修改,最后由主进程广播至所有子进程。
147
+ config.configset("master_status", "ok")
148
+
149
+ # 6. 清理
150
+ main_pipe.close()
151
+ ```
152
+
153
+ ### 子进程
154
+
155
+ ```python
156
+ from config import ConfigInChildProcessPipe
157
+
158
+ def worker(set_queue, get_queue):
159
+ # 使用主进程传入的队列初始化
160
+ config = ConfigInChildProcessPipe(set_queue, get_queue)
161
+
162
+ # 读取配置(从本地缓存读取,无 I/O 开销)
163
+ print(config.configget("workers")) # 4
164
+
165
+ # 修改配置(发送消息给主进程,由主进程执行实际写入)
166
+ config.configset("worker_status", "done")
167
+
168
+ # 主进程的修改会自动同步到本地缓存
169
+
170
+ # 使用完毕后关闭
171
+ config.close()
172
+ ```
173
+
174
+ ### 架构说明
175
+
176
+ ```
177
+ 子进程 configset() ──→ set_queue ──→ 主进程 _loop() ──→ Config.configset()
178
+ │
179
+ ▼
180
+ config_get_queue
181
+ │
182
+ ▼
183
+ 子进程 _update_config_loop()
184
+ 更新本地 self.config 缓存
185
+ ```
186
+
187
+ - 子进程的 `configset()` 和 `configreset()` 不直接写文件,而是通过队列将请求发送给主进程
188
+ - 主进程统一处理所有写操作和文件 I/O,修改后通过广播队列通知所有子进程
189
+ - 子进程的 `configget()` 从本地缓存读取,无跨进程开销
190
+ - 每个子进程需要独立的管道对(一个 `ConfigInMainProcessPipe` + 一个 `ConfigInChildProcessPipe`)
191
+
192
+ ## API 参考
193
+
194
+ ### Config
195
+
196
+ 核心配置类,管理 JSON 文件的读写。
197
+
198
+ ```python
199
+ Config(file_name="config", default_config=None, default_config_callback=None, init_use_callback=True)
200
+ ```
201
+
202
+ | 参数 | 类型 | 默认值 | 说明 |
203
+ |------|------|--------|------|
204
+ | `file_name` | `str` | `"config"` | 配置文件名,不含 `.json` 扩展名 |
205
+ | `default_config` | `dict` | `None` | 默认配置字典,缺失的 key 自动补齐 |
206
+ | `default_config_callback` | `Callable` | `None` | 默认配置回调函数,重置时动态生成默认值。接受当前 config 字典作为参数 |
207
+ | `init_use_callback` | `bool` | `True` | 是否在初始化时调用回调来填充 `default_config` |
208
+
209
+ **方法:**
210
+
211
+ | 方法 | 说明 |
212
+ |------|------|
213
+ | `configget(key)` | 获取配置值。`key="all"` 返回全部配置 |
214
+ | `configset(key, value)` | 设置配置值,自动保存到文件并通知子进程 |
215
+ | `configreset(key)` | 恢复默认值。`key="all"` 恢复全部 |
216
+ | `configupdate_config(key, last_value=None)` | 更新默认值,用于版本更新时希望修改软件的默认值。若用户未修改过该 key 则同步为新默认值。需要手动创建一项"{原配置key}_reseted"=False的配置,用于标记是否执行过更新默认值的操作。 |
217
+ | `update_config()` | 向所有已注册的子进程广播当前配置 |
218
+
219
+ ### ConfigInMainProcessPipe
220
+
221
+ 主进程管道接口,在主进程中每个子进程对应创建一个实例。
222
+
223
+ ```python
224
+ ConfigInMainProcessPipe(config: Config)
225
+ ```
226
+
227
+ | 方法 | 说明 |
228
+ |------|------|
229
+ | `get_queues()` | 返回 `(set_queue, get_queue)` 元组,传递给子进程 |
230
+ | `close()` | 关闭管道,停止监听线程 |
231
+
232
+ ### ConfigInChildProcessPipe
233
+
234
+ 子进程配置工具,在子进程中实例化,通过管道与主进程通信。
235
+
236
+ ```python
237
+ ConfigInChildProcessPipe(config_set_queue, config_get_queue)
238
+ ```
239
+
240
+ | 方法 | 说明 |
241
+ |------|------|
242
+ | `configget(key)` | 从本地缓存读取配置值 |
243
+ | `configset(key, value)` | 修改配置(消息发送给主进程执行) |
244
+ | `configreset(key)` | 恢复默认值(消息发送给主进程执行) |
245
+ | `close()` | 关闭管道,停止同步线程 |
246
+
247
+ ## 注意事项
248
+
249
+ 1. **多进程环境下请保持 Config 单例** — 多个 Config 实例同时操作同一文件会导致数据不一致
250
+ 2. **子进程中的 ConfigInChildProcessPipe 不直接操作文件** — 所有写操作将发送请求,由主进程统一处理
251
+ 3. **使用完毕后请调用 `close()`** — 避免守护线程未正常退出
252
+ 4. **配置文件名不含 `.json` 后缀** — 传入 `"myconfig"` 对应文件 `myconfig.json`
253
+
254
+ ## 许可证
255
+
256
+ MIT
@@ -0,0 +1,234 @@
1
+ # xiaoe-config-manager
2
+
3
+ 多进程安全的 JSON 配置文件管理器,纯 Python 标准库实现,零外部依赖。
4
+
5
+ ## 特性
6
+
7
+ - **线程安全** — 内置线程锁,单进程多线程环境下安全读写
8
+ - **多进程安全** — 通过 `multiprocessing.Queue` 双向管道实现主进程与子进程之间的配置自动同步
9
+ - **默认配置** — 支持静态默认字典和动态回调两种方式,配置项缺失时自动补齐
10
+ - **配置重置** — 支持单个 key 或全部配置一键恢复默认值
11
+ - **默认值热更新** — 当默认配置变化时,自动识别用户未修改的项并更新为新默认值
12
+ - **纯标准库** — 无任何第三方依赖,仅使用 `json`、`threading`、`multiprocessing` 等标准库
13
+
14
+ ## 安装
15
+
16
+ ```bash
17
+ pip install xiaoe-config-manager
18
+ ```
19
+
20
+ ## 快速开始
21
+
22
+ ### 单进程使用
23
+
24
+ ```python
25
+ from config import Config
26
+
27
+ # 初始化,指定配置文件名(不含扩展名)
28
+ config = Config("app_config")
29
+
30
+ # 写入配置
31
+ config.configset("api_url", "https://api.example.com")
32
+ config.configset("timeout", 30)
33
+
34
+ # 读取配置
35
+ print(config.configget("api_url")) # https://api.example.com
36
+
37
+ # 读取全部配置
38
+ all_config = config.configget("all")
39
+ print(all_config) # {"api_url": "https://api.example.com", "timeout": 30}
40
+ ```
41
+
42
+ ### 带默认配置
43
+
44
+ ```python
45
+ from config import Config
46
+
47
+ defaults = {
48
+ "host": "0.0.0.0",
49
+ "port": 8080,
50
+ "debug": False,
51
+ }
52
+
53
+ config = Config("server_config", default_config=defaults)
54
+
55
+ # 配置文件中已有的键保留原值,缺失的键自动用默认值补齐
56
+ print(config.configget("host")) # "0.0.0.0"
57
+ print(config.configget("port")) # 8080
58
+
59
+ # 恢复单个配置项为默认值
60
+ config.configreset("port")
61
+
62
+ # 恢复全部配置为默认值
63
+ config.configreset("all")
64
+ ```
65
+
66
+ ### 使用回调函数动态生成默认配置
67
+
68
+ ```python
69
+ from config import Config
70
+
71
+ def dynamic_defaults(current_config):
72
+ """根据当前配置动态生成默认值"""
73
+ import socket
74
+ return {
75
+ "host": "0.0.0.0",
76
+ "port": 8080,
77
+ "hostname": socket.gethostname(),
78
+ "worker_count": 4,
79
+ }
80
+
81
+ config = Config(
82
+ "dynamic_config",
83
+ default_config_callback=dynamic_defaults,
84
+ init_use_callback=True, # 初始化时调用回调
85
+ )
86
+
87
+ # 重置时会再次调用回调,获取最新的默认值
88
+ config.configreset("all")
89
+ ```
90
+
91
+ ## 多进程使用
92
+
93
+ 多进程场景下,**主进程持有 `Config` 单例**,子进程通过 `ConfigInChildProcessPipe` 与主进程通信。主进程需要为每个子进程创建一个 `ConfigInMainProcessPipe` 实例。
94
+
95
+ ### 主进程
96
+
97
+ ```python
98
+ from config import Config, ConfigInMainProcessPipe
99
+
100
+ # 1. 创建配置单例
101
+ config = Config("my_config", default_config={"workers": 4})
102
+
103
+ # 2. 为子进程创建主进程端的管道
104
+ main_pipe = ConfigInMainProcessPipe(config)
105
+
106
+ # 3. 获取队列,传递给子进程
107
+ set_queue, get_queue = main_pipe.get_queues()
108
+
109
+ # 4. 启动子进程(以 multiprocessing.Process 为例)
110
+ import multiprocessing
111
+
112
+ def worker(set_q, get_q):
113
+ from config import ConfigInChildProcessPipe
114
+ # 5. 为子进程创建子进程端的管道
115
+ child_config = ConfigInChildProcessPipe(set_q, get_q)
116
+ child_config.configset("worker_status", "running")
117
+ child_config.close()
118
+
119
+ p = multiprocessing.Process(target=worker, args=(set_queue, get_queue))
120
+ p.start()
121
+ p.join()
122
+
123
+ # 6. 主进程可直接读写配置,修改会自动同步并广播到到子进程。
124
+ # 同样,子进程的修改请求会提交至主进程修改,最后由主进程广播至所有子进程。
125
+ config.configset("master_status", "ok")
126
+
127
+ # 6. 清理
128
+ main_pipe.close()
129
+ ```
130
+
131
+ ### 子进程
132
+
133
+ ```python
134
+ from config import ConfigInChildProcessPipe
135
+
136
+ def worker(set_queue, get_queue):
137
+ # 使用主进程传入的队列初始化
138
+ config = ConfigInChildProcessPipe(set_queue, get_queue)
139
+
140
+ # 读取配置(从本地缓存读取,无 I/O 开销)
141
+ print(config.configget("workers")) # 4
142
+
143
+ # 修改配置(发送消息给主进程,由主进程执行实际写入)
144
+ config.configset("worker_status", "done")
145
+
146
+ # 主进程的修改会自动同步到本地缓存
147
+
148
+ # 使用完毕后关闭
149
+ config.close()
150
+ ```
151
+
152
+ ### 架构说明
153
+
154
+ ```
155
+ 子进程 configset() ──→ set_queue ──→ 主进程 _loop() ──→ Config.configset()
156
+ │
157
+ ▼
158
+ config_get_queue
159
+ │
160
+ ▼
161
+ 子进程 _update_config_loop()
162
+ 更新本地 self.config 缓存
163
+ ```
164
+
165
+ - 子进程的 `configset()` 和 `configreset()` 不直接写文件,而是通过队列将请求发送给主进程
166
+ - 主进程统一处理所有写操作和文件 I/O,修改后通过广播队列通知所有子进程
167
+ - 子进程的 `configget()` 从本地缓存读取,无跨进程开销
168
+ - 每个子进程需要独立的管道对(一个 `ConfigInMainProcessPipe` + 一个 `ConfigInChildProcessPipe`)
169
+
170
+ ## API 参考
171
+
172
+ ### Config
173
+
174
+ 核心配置类,管理 JSON 文件的读写。
175
+
176
+ ```python
177
+ Config(file_name="config", default_config=None, default_config_callback=None, init_use_callback=True)
178
+ ```
179
+
180
+ | 参数 | 类型 | 默认值 | 说明 |
181
+ |------|------|--------|------|
182
+ | `file_name` | `str` | `"config"` | 配置文件名,不含 `.json` 扩展名 |
183
+ | `default_config` | `dict` | `None` | 默认配置字典,缺失的 key 自动补齐 |
184
+ | `default_config_callback` | `Callable` | `None` | 默认配置回调函数,重置时动态生成默认值。接受当前 config 字典作为参数 |
185
+ | `init_use_callback` | `bool` | `True` | 是否在初始化时调用回调来填充 `default_config` |
186
+
187
+ **方法:**
188
+
189
+ | 方法 | 说明 |
190
+ |------|------|
191
+ | `configget(key)` | 获取配置值。`key="all"` 返回全部配置 |
192
+ | `configset(key, value)` | 设置配置值,自动保存到文件并通知子进程 |
193
+ | `configreset(key)` | 恢复默认值。`key="all"` 恢复全部 |
194
+ | `configupdate_config(key, last_value=None)` | 更新默认值,用于版本更新时希望修改软件的默认值。若用户未修改过该 key 则同步为新默认值。需要手动创建一项"{原配置key}_reseted"=False的配置,用于标记是否执行过更新默认值的操作。 |
195
+ | `update_config()` | 向所有已注册的子进程广播当前配置 |
196
+
197
+ ### ConfigInMainProcessPipe
198
+
199
+ 主进程管道接口,在主进程中每个子进程对应创建一个实例。
200
+
201
+ ```python
202
+ ConfigInMainProcessPipe(config: Config)
203
+ ```
204
+
205
+ | 方法 | 说明 |
206
+ |------|------|
207
+ | `get_queues()` | 返回 `(set_queue, get_queue)` 元组,传递给子进程 |
208
+ | `close()` | 关闭管道,停止监听线程 |
209
+
210
+ ### ConfigInChildProcessPipe
211
+
212
+ 子进程配置工具,在子进程中实例化,通过管道与主进程通信。
213
+
214
+ ```python
215
+ ConfigInChildProcessPipe(config_set_queue, config_get_queue)
216
+ ```
217
+
218
+ | 方法 | 说明 |
219
+ |------|------|
220
+ | `configget(key)` | 从本地缓存读取配置值 |
221
+ | `configset(key, value)` | 修改配置(消息发送给主进程执行) |
222
+ | `configreset(key)` | 恢复默认值(消息发送给主进程执行) |
223
+ | `close()` | 关闭管道,停止同步线程 |
224
+
225
+ ## 注意事项
226
+
227
+ 1. **多进程环境下请保持 Config 单例** — 多个 Config 实例同时操作同一文件会导致数据不一致
228
+ 2. **子进程中的 ConfigInChildProcessPipe 不直接操作文件** — 所有写操作将发送请求,由主进程统一处理
229
+ 3. **使用完毕后请调用 `close()`** — 避免守护线程未正常退出
230
+ 4. **配置文件名不含 `.json` 后缀** — 传入 `"myconfig"` 对应文件 `myconfig.json`
231
+
232
+ ## 许可证
233
+
234
+ MIT
@@ -0,0 +1,10 @@
1
+ """
2
+ 多进程安全配置管理模块
3
+
4
+ 包含:
5
+ - Config 类:配置文件核心操作(可以单独使用,也可以配合 ConfigInMainProcessPipe 和 ConfigInChildProcessPipe 使用)
6
+ - ConfigInMainProcessPipe 类:主进程管道接口(结束时调用close())
7
+ - ConfigInChildProcessPipe 类:子进程操作工具(结束时调用close())
8
+ """
9
+ from .config import Config
10
+ from .config_multiprocess import ConfigInMainProcessPipe, ConfigInChildProcessPipe
@@ -0,0 +1,102 @@
1
+ import copy
2
+ import json
3
+ import threading
4
+ from typing import Callable
5
+
6
+
7
+ class Config:
8
+ """
9
+ 如果多进程环境下,请保持单例模式。
10
+ """
11
+ def __init__(self,file_name='config' ,default_config: dict = None,
12
+ default_config_callback: Callable =None, init_use_callback : bool =True,):
13
+ """
14
+ 初始化配置
15
+ Args:
16
+ file_name: 配置文件名
17
+ default_config: 默认配置(如果配置项多出,则会自动添加)
18
+ default_config_callback: 默认配置回调函数(重置时可动态获取默认配置)
19
+ init_use_callback: 是否在初始化时使用回调函数获取默认配置
20
+ """
21
+ self.fname = file_name
22
+ self.queues = []# 当配置被修改时,向所有队列发送配置
23
+ self.default_config = default_config
24
+ self.default_config_callback = default_config_callback
25
+ self.config_lock = threading.Lock() # 因为单个进程保持单例,使用多线程锁即可
26
+ try:
27
+ self.config = self.configget('all')
28
+ except Exception:
29
+ self.config = {}
30
+ if self.default_config_callback and init_use_callback:
31
+ self.default_config = self.default_config_callback(self.config)
32
+ for k, v in copy.deepcopy(self.default_config).items():
33
+ self._config_add(k, v)
34
+
35
+
36
+
37
+ def update_config(self):
38
+ """
39
+ 向所有进程发送更新的配置
40
+ """
41
+ for _, get_queue in self.queues:
42
+ get_queue.put(self.config)
43
+
44
+
45
+ def _json_save(self):
46
+ """修改设置(key,value,a)(要修改的项,值,all或1)"""
47
+ with open(f"{self.fname}.json", 'w', encoding='UTF-8') as f:
48
+ json.dump(self.config, f, indent=4, ensure_ascii=False)
49
+
50
+
51
+
52
+ def configget(self, key):
53
+ """获取设置 in:key out:value"""
54
+ if key == 'all':
55
+ with open(f"{self.fname}.json", 'r', encoding='UTF-8') as f:
56
+ self.config = json.load(f)
57
+ return self.config
58
+ return self.config[key]
59
+
60
+
61
+
62
+ def configset(self, key, value):
63
+ """修改设置"""
64
+ with self.config_lock:
65
+ self.config[key] = value
66
+ self._json_save()
67
+ self.update_config()
68
+
69
+
70
+ def configreset(self, key):
71
+ """恢复默认设置"""
72
+ with self.config_lock:
73
+ print(f"reset config {key}")
74
+ if self.default_config_callback:
75
+ self.default_config = self.default_config_callback(
76
+ None if key == 'all' else self.config
77
+ )
78
+ default_config = copy.deepcopy(self.default_config)
79
+ if key == 'all':
80
+ self.config = default_config
81
+ else:
82
+ self.config[key] = default_config[key]
83
+ self._json_save()
84
+ self.update_config()
85
+
86
+ def configupdate_config(self, key, last_value =None):
87
+ """
88
+ 更新默认值, 如果用户的配置与旧的默认值一致,则更新为新的默认值
89
+ Args:
90
+ key: 需要更新的配置项
91
+ last_value: 旧的默认值
92
+ """
93
+ if not self.configget(f"{key}_reseted"):
94
+ if last_value is None or self.configget(key) == last_value:
95
+ self.configreset(key)
96
+ self.configset(f"{key}_reseted", True)
97
+ def _config_add(self, key, default_value=None):
98
+ if key not in self.config:
99
+ self.config[key] = default_value
100
+ self._json_save()
101
+
102
+
@@ -0,0 +1,127 @@
1
+ # 多进程配置模块
2
+ # 主进程中实例化ConfigProcess,子进程中实例化ConfigProcessTools。
3
+ # 通过双向管道实现子进程对主进程配置文件的读写。
4
+ import multiprocessing
5
+ import threading
6
+ import traceback
7
+
8
+ from .config import Config
9
+
10
+
11
+ class ConfigInMainProcessPipe:
12
+ """
13
+ config多进程的主进程管道口。
14
+ 每个进程都需要在主进程实例化一个。
15
+ """
16
+ def __init__(self, config: Config):
17
+ """
18
+ 初始化
19
+ Args:
20
+ config: 主进程中的配置单例
21
+ """
22
+ self.config = config
23
+ self.config_set_queue = multiprocessing.Queue()# 子进程-》主进程
24
+ self.config_get_queue = multiprocessing.Queue()# 主进程-》子进程
25
+ # 注册队列
26
+ # 当config被修改时,通过这个队列向所有进程发送新配置
27
+ self.config.queues.append((
28
+ self.config_set_queue,
29
+ self.config_get_queue
30
+ ))
31
+
32
+ self._loop_thread = threading.Thread(target=self._loop, daemon=True)
33
+ self._loop_thread.start()
34
+
35
+ def get_queues(self):
36
+ """将这两个对象传递给子进程"""
37
+ return self.config_set_queue, self.config_get_queue
38
+
39
+ def _loop(self):
40
+ while True:
41
+ msg = self.config_set_queue.get()
42
+ if msg is None:
43
+ break
44
+ args = msg.get("args", [])
45
+ if msg['call'] == 'configset':
46
+ self.config.configset(*args)
47
+ # if msg['call'] == 'configget':
48
+ # self.config_get_queue.put(self.config.configget(*args))
49
+ if msg['call'] == 'configreset':
50
+ self.config.configreset(*args)
51
+ if msg['call'] == 'configgetall':
52
+ self.config_get_queue.put(self.config.config)
53
+ # 当有进程修改了配置,通知所有进程更新配置
54
+
55
+ def close(self):
56
+ try:
57
+ self.config_set_queue.put(None)
58
+ self._loop_thread.join()
59
+ except Exception:
60
+ traceback.print_exc()
61
+
62
+
63
+ class ConfigInChildProcessPipe:
64
+ """
65
+ 在子进程中实例化,与主进程中的ConfigProcess通信。
66
+ 通过传入双向管道实现子进程对配置文件的读写。
67
+ """
68
+ def __init__(
69
+ self,
70
+ config_set_queue: multiprocessing.Queue,
71
+ config_get_queue: multiprocessing.Queue,
72
+ ):
73
+ """
74
+ 初始化
75
+ Args:
76
+ config_set_queue: 主进程-》子进程(当config被修改时,接收修改消息)
77
+ config_get_queue: 子进程-》主进程
78
+ """
79
+
80
+ self.config_set_queue = config_set_queue
81
+ self.config_get_queue = config_get_queue
82
+ self.config = {}
83
+ self._init_config()
84
+ print('success init config process tools')
85
+ self._update_config_loop_thread = threading.Thread(target=self._update_config_loop, daemon=True)
86
+ self._update_config_loop_thread.start()
87
+
88
+ def configget(self, key):
89
+ """获取设置 in:key out:value"""
90
+ result = self.config.get(key)
91
+ return result
92
+
93
+ def configset(self, key, value):
94
+ """修改设置"""
95
+ self.config[key] = value
96
+ self.config_set_queue.put({
97
+ 'call': 'configset',
98
+ 'args': [key, value],
99
+ })
100
+
101
+ def configreset(self, key):
102
+ """恢复默认设置"""
103
+ self.config_set_queue.put({
104
+ 'call': 'configreset',
105
+ 'args': [key],
106
+ })
107
+
108
+ def _init_config(self):
109
+ self.config_set_queue.put({
110
+ 'call': 'configgetall',
111
+ })
112
+ self.config = self.config_get_queue.get()
113
+
114
+ def _update_config_loop(self):
115
+ """收到更新配置的消息,更新本地配置"""
116
+ while True:
117
+ config = self.config_get_queue.get()
118
+ if config is None:
119
+ break
120
+ self.config = config
121
+
122
+ def close(self):
123
+ try:
124
+ self.config_get_queue.put(None)
125
+ self._update_config_loop_thread.join()
126
+ except Exception:
127
+ traceback.print_exc()
@@ -0,0 +1,32 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "xiaoe-config-manager"
7
+ version = "1.0.0"
8
+ description = "多进程安全的 JSON 配置文件管理器,支持默认配置、动态重置与进程间自动同步"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.8"
12
+ keywords = ["config", "configuration", "json", "multiprocess", "thread-safe"]
13
+ classifiers = [
14
+ "Development Status :: 4 - Beta",
15
+ "Intended Audience :: Developers",
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3.8",
18
+ "Programming Language :: Python :: 3.9",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Operating System :: OS Independent",
24
+ "Topic :: Software Development :: Libraries :: Python Modules",
25
+ ]
26
+
27
+ [project.urls]
28
+ Homepage = "https://github.com/xiaoe/xiaoe-config-manager"
29
+ Repository = "https://github.com/xiaoe/xiaoe-config-manager"
30
+
31
+ [tool.setuptools]
32
+ packages = ["config"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,256 @@
1
+ Metadata-Version: 2.4
2
+ Name: xiaoe-config-manager
3
+ Version: 1.0.0
4
+ Summary: 多进程安全的 JSON 配置文件管理器,支持默认配置、动态重置与进程间自动同步
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://github.com/xiaoe/xiaoe-config-manager
7
+ Project-URL: Repository, https://github.com/xiaoe/xiaoe-config-manager
8
+ Keywords: config,configuration,json,multiprocess,thread-safe
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.8
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Requires-Python: >=3.8
21
+ Description-Content-Type: text/markdown
22
+
23
+ # xiaoe-config-manager
24
+
25
+ 多进程安全的 JSON 配置文件管理器,纯 Python 标准库实现,零外部依赖。
26
+
27
+ ## 特性
28
+
29
+ - **线程安全** — 内置线程锁,单进程多线程环境下安全读写
30
+ - **多进程安全** — 通过 `multiprocessing.Queue` 双向管道实现主进程与子进程之间的配置自动同步
31
+ - **默认配置** — 支持静态默认字典和动态回调两种方式,配置项缺失时自动补齐
32
+ - **配置重置** — 支持单个 key 或全部配置一键恢复默认值
33
+ - **默认值热更新** — 当默认配置变化时,自动识别用户未修改的项并更新为新默认值
34
+ - **纯标准库** — 无任何第三方依赖,仅使用 `json`、`threading`、`multiprocessing` 等标准库
35
+
36
+ ## 安装
37
+
38
+ ```bash
39
+ pip install xiaoe-config-manager
40
+ ```
41
+
42
+ ## 快速开始
43
+
44
+ ### 单进程使用
45
+
46
+ ```python
47
+ from config import Config
48
+
49
+ # 初始化,指定配置文件名(不含扩展名)
50
+ config = Config("app_config")
51
+
52
+ # 写入配置
53
+ config.configset("api_url", "https://api.example.com")
54
+ config.configset("timeout", 30)
55
+
56
+ # 读取配置
57
+ print(config.configget("api_url")) # https://api.example.com
58
+
59
+ # 读取全部配置
60
+ all_config = config.configget("all")
61
+ print(all_config) # {"api_url": "https://api.example.com", "timeout": 30}
62
+ ```
63
+
64
+ ### 带默认配置
65
+
66
+ ```python
67
+ from config import Config
68
+
69
+ defaults = {
70
+ "host": "0.0.0.0",
71
+ "port": 8080,
72
+ "debug": False,
73
+ }
74
+
75
+ config = Config("server_config", default_config=defaults)
76
+
77
+ # 配置文件中已有的键保留原值,缺失的键自动用默认值补齐
78
+ print(config.configget("host")) # "0.0.0.0"
79
+ print(config.configget("port")) # 8080
80
+
81
+ # 恢复单个配置项为默认值
82
+ config.configreset("port")
83
+
84
+ # 恢复全部配置为默认值
85
+ config.configreset("all")
86
+ ```
87
+
88
+ ### 使用回调函数动态生成默认配置
89
+
90
+ ```python
91
+ from config import Config
92
+
93
+ def dynamic_defaults(current_config):
94
+ """根据当前配置动态生成默认值"""
95
+ import socket
96
+ return {
97
+ "host": "0.0.0.0",
98
+ "port": 8080,
99
+ "hostname": socket.gethostname(),
100
+ "worker_count": 4,
101
+ }
102
+
103
+ config = Config(
104
+ "dynamic_config",
105
+ default_config_callback=dynamic_defaults,
106
+ init_use_callback=True, # 初始化时调用回调
107
+ )
108
+
109
+ # 重置时会再次调用回调,获取最新的默认值
110
+ config.configreset("all")
111
+ ```
112
+
113
+ ## 多进程使用
114
+
115
+ 多进程场景下,**主进程持有 `Config` 单例**,子进程通过 `ConfigInChildProcessPipe` 与主进程通信。主进程需要为每个子进程创建一个 `ConfigInMainProcessPipe` 实例。
116
+
117
+ ### 主进程
118
+
119
+ ```python
120
+ from config import Config, ConfigInMainProcessPipe
121
+
122
+ # 1. 创建配置单例
123
+ config = Config("my_config", default_config={"workers": 4})
124
+
125
+ # 2. 为子进程创建主进程端的管道
126
+ main_pipe = ConfigInMainProcessPipe(config)
127
+
128
+ # 3. 获取队列,传递给子进程
129
+ set_queue, get_queue = main_pipe.get_queues()
130
+
131
+ # 4. 启动子进程(以 multiprocessing.Process 为例)
132
+ import multiprocessing
133
+
134
+ def worker(set_q, get_q):
135
+ from config import ConfigInChildProcessPipe
136
+ # 5. 为子进程创建子进程端的管道
137
+ child_config = ConfigInChildProcessPipe(set_q, get_q)
138
+ child_config.configset("worker_status", "running")
139
+ child_config.close()
140
+
141
+ p = multiprocessing.Process(target=worker, args=(set_queue, get_queue))
142
+ p.start()
143
+ p.join()
144
+
145
+ # 6. 主进程可直接读写配置,修改会自动同步并广播到到子进程。
146
+ # 同样,子进程的修改请求会提交至主进程修改,最后由主进程广播至所有子进程。
147
+ config.configset("master_status", "ok")
148
+
149
+ # 6. 清理
150
+ main_pipe.close()
151
+ ```
152
+
153
+ ### 子进程
154
+
155
+ ```python
156
+ from config import ConfigInChildProcessPipe
157
+
158
+ def worker(set_queue, get_queue):
159
+ # 使用主进程传入的队列初始化
160
+ config = ConfigInChildProcessPipe(set_queue, get_queue)
161
+
162
+ # 读取配置(从本地缓存读取,无 I/O 开销)
163
+ print(config.configget("workers")) # 4
164
+
165
+ # 修改配置(发送消息给主进程,由主进程执行实际写入)
166
+ config.configset("worker_status", "done")
167
+
168
+ # 主进程的修改会自动同步到本地缓存
169
+
170
+ # 使用完毕后关闭
171
+ config.close()
172
+ ```
173
+
174
+ ### 架构说明
175
+
176
+ ```
177
+ 子进程 configset() ──→ set_queue ──→ 主进程 _loop() ──→ Config.configset()
178
+ │
179
+ ▼
180
+ config_get_queue
181
+ │
182
+ ▼
183
+ 子进程 _update_config_loop()
184
+ 更新本地 self.config 缓存
185
+ ```
186
+
187
+ - 子进程的 `configset()` 和 `configreset()` 不直接写文件,而是通过队列将请求发送给主进程
188
+ - 主进程统一处理所有写操作和文件 I/O,修改后通过广播队列通知所有子进程
189
+ - 子进程的 `configget()` 从本地缓存读取,无跨进程开销
190
+ - 每个子进程需要独立的管道对(一个 `ConfigInMainProcessPipe` + 一个 `ConfigInChildProcessPipe`)
191
+
192
+ ## API 参考
193
+
194
+ ### Config
195
+
196
+ 核心配置类,管理 JSON 文件的读写。
197
+
198
+ ```python
199
+ Config(file_name="config", default_config=None, default_config_callback=None, init_use_callback=True)
200
+ ```
201
+
202
+ | 参数 | 类型 | 默认值 | 说明 |
203
+ |------|------|--------|------|
204
+ | `file_name` | `str` | `"config"` | 配置文件名,不含 `.json` 扩展名 |
205
+ | `default_config` | `dict` | `None` | 默认配置字典,缺失的 key 自动补齐 |
206
+ | `default_config_callback` | `Callable` | `None` | 默认配置回调函数,重置时动态生成默认值。接受当前 config 字典作为参数 |
207
+ | `init_use_callback` | `bool` | `True` | 是否在初始化时调用回调来填充 `default_config` |
208
+
209
+ **方法:**
210
+
211
+ | 方法 | 说明 |
212
+ |------|------|
213
+ | `configget(key)` | 获取配置值。`key="all"` 返回全部配置 |
214
+ | `configset(key, value)` | 设置配置值,自动保存到文件并通知子进程 |
215
+ | `configreset(key)` | 恢复默认值。`key="all"` 恢复全部 |
216
+ | `configupdate_config(key, last_value=None)` | 更新默认值,用于版本更新时希望修改软件的默认值。若用户未修改过该 key 则同步为新默认值。需要手动创建一项"{原配置key}_reseted"=False的配置,用于标记是否执行过更新默认值的操作。 |
217
+ | `update_config()` | 向所有已注册的子进程广播当前配置 |
218
+
219
+ ### ConfigInMainProcessPipe
220
+
221
+ 主进程管道接口,在主进程中每个子进程对应创建一个实例。
222
+
223
+ ```python
224
+ ConfigInMainProcessPipe(config: Config)
225
+ ```
226
+
227
+ | 方法 | 说明 |
228
+ |------|------|
229
+ | `get_queues()` | 返回 `(set_queue, get_queue)` 元组,传递给子进程 |
230
+ | `close()` | 关闭管道,停止监听线程 |
231
+
232
+ ### ConfigInChildProcessPipe
233
+
234
+ 子进程配置工具,在子进程中实例化,通过管道与主进程通信。
235
+
236
+ ```python
237
+ ConfigInChildProcessPipe(config_set_queue, config_get_queue)
238
+ ```
239
+
240
+ | 方法 | 说明 |
241
+ |------|------|
242
+ | `configget(key)` | 从本地缓存读取配置值 |
243
+ | `configset(key, value)` | 修改配置(消息发送给主进程执行) |
244
+ | `configreset(key)` | 恢复默认值(消息发送给主进程执行) |
245
+ | `close()` | 关闭管道,停止同步线程 |
246
+
247
+ ## 注意事项
248
+
249
+ 1. **多进程环境下请保持 Config 单例** — 多个 Config 实例同时操作同一文件会导致数据不一致
250
+ 2. **子进程中的 ConfigInChildProcessPipe 不直接操作文件** — 所有写操作将发送请求,由主进程统一处理
251
+ 3. **使用完毕后请调用 `close()`** — 避免守护线程未正常退出
252
+ 4. **配置文件名不含 `.json` 后缀** — 传入 `"myconfig"` 对应文件 `myconfig.json`
253
+
254
+ ## 许可证
255
+
256
+ MIT
@@ -0,0 +1,9 @@
1
+ README.md
2
+ pyproject.toml
3
+ config/__init__.py
4
+ config/config.py
5
+ config/config_multiprocess.py
6
+ xiaoe_config_manager.egg-info/PKG-INFO
7
+ xiaoe_config_manager.egg-info/SOURCES.txt
8
+ xiaoe_config_manager.egg-info/dependency_links.txt
9
+ xiaoe_config_manager.egg-info/top_level.txt