1
1
mirror of https://github.com/KenanZhu/AutoLibrary.git synced 2026-06-18 07:23:03 +08:00

refactor(config): 引入 ConfigPath 值对象消除 ConfigType/ConfigKey 的消费者 API 冗余

This commit is contained in:
2026-05-10 16:14:20 +08:00
parent bbd97970a6
commit 14c6db3384
6 changed files with 155 additions and 33 deletions
+6 -5
View File
@@ -24,6 +24,7 @@ import managers.config.ConfigManager as ConfigManager
from utils.JSONReader import JSONReader from utils.JSONReader import JSONReader
from utils.JSONWriter import JSONWriter from utils.JSONWriter import JSONWriter
from interfaces.ConfigProvider import ConfigProvider, CfgKey
from managers.config.ConfigUtils import ConfigUtils from managers.config.ConfigUtils import ConfigUtils
from gui.resources.ui.Ui_ALConfigWidget import Ui_ALConfigWidget from gui.resources.ui.Ui_ALConfigWidget import Ui_ALConfigWidget
@@ -43,7 +44,7 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
): ):
super().__init__(parent) super().__init__(parent)
self.__cfg_mgr = ConfigManager.instance() self.__cfg_mgr: ConfigProvider = ConfigManager.instance()
self.__config_paths = ConfigUtils.getAutomationConfigPaths() self.__config_paths = ConfigUtils.getAutomationConfigPaths()
self.__config_data = {"run": {}, "user": {}} self.__config_data = {"run": {}, "user": {}}
@@ -985,13 +986,13 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
self.setRunConfigToWidget(data) self.setRunConfigToWidget(data)
self.__config_paths["run"] = run_config_path self.__config_paths["run"] = run_config_path
self.CurrentRunConfigEdit.setText(run_config_path) self.CurrentRunConfigEdit.setText(run_config_path)
paths = self.__cfg_mgr.get(ConfigManager.ConfigType.GLOBAL, "automation.run_path.paths", []) paths = self.__cfg_mgr.get(CfgKey.GLOBAL.AUTOMATION.RUN_PATH.PATHS, [])
if run_config_path not in paths: if run_config_path not in paths:
paths.append(run_config_path) paths.append(run_config_path)
index = len(paths) - 1 index = len(paths) - 1
else: else:
index = paths.index(run_config_path) index = paths.index(run_config_path)
self.__cfg_mgr.set(ConfigManager.ConfigType.GLOBAL, "automation.run_path", {"current": index, "paths": paths}) self.__cfg_mgr.set(CfgKey.GLOBAL.AUTOMATION.RUN_PATH.ROOT, {"current": index, "paths": paths})
else: else:
QMessageBox.warning( QMessageBox.warning(
self, self,
@@ -1020,13 +1021,13 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
self.setUsersToTreeWidget(data) self.setUsersToTreeWidget(data)
self.__config_paths["user"] = user_config_path self.__config_paths["user"] = user_config_path
self.CurrentUserConfigEdit.setText(user_config_path) self.CurrentUserConfigEdit.setText(user_config_path)
paths = self.__cfg_mgr.get(ConfigManager.ConfigType.GLOBAL, "automation.user_path.paths", []) paths = self.__cfg_mgr.get(CfgKey.GLOBAL.AUTOMATION.USER_PATH.PATHS, [])
if user_config_path not in paths: if user_config_path not in paths:
paths.append(user_config_path) paths.append(user_config_path)
index = len(paths) - 1 index = len(paths) - 1
else: else:
index = paths.index(user_config_path) index = paths.index(user_config_path)
self.__cfg_mgr.set(ConfigManager.ConfigType.GLOBAL, "automation.user_path", {"current": index, "paths": paths}) self.__cfg_mgr.set(CfgKey.GLOBAL.AUTOMATION.USER_PATH.ROOT, {"current": index, "paths": paths})
else: else:
QMessageBox.warning( QMessageBox.warning(
self, self,
+6 -4
View File
@@ -26,7 +26,9 @@ from PySide6.QtGui import (
) )
import managers.config.ConfigManager as ConfigManager import managers.config.ConfigManager as ConfigManager
from utils.TimerUtils import TimerUtils from utils.TimerUtils import TimerUtils
from interfaces.ConfigProvider import ConfigProvider, CfgKey
from gui.resources.ui.Ui_ALTimerTaskManageWidget import Ui_ALTimerTaskManageWidget from gui.resources.ui.Ui_ALTimerTaskManageWidget import Ui_ALTimerTaskManageWidget
from gui.ALTimerTaskAddDialog import ALTimerTaskAddDialog, ALTimerTaskStatus from gui.ALTimerTaskAddDialog import ALTimerTaskAddDialog, ALTimerTaskStatus
@@ -190,7 +192,7 @@ class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget):
): ):
super().__init__(parent) super().__init__(parent)
self.__cfg_mgr = ConfigManager.instance() self.__cfg_mgr: ConfigProvider = ConfigManager.instance()
self.__timer_tasks = [] self.__timer_tasks = []
self.__check_timer = None self.__check_timer = None
self.__sort_policy = self.SortPolicy.BY_EXECUTE_TIME self.__sort_policy = self.SortPolicy.BY_EXECUTE_TIME
@@ -244,7 +246,7 @@ class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget):
) -> list: ) -> list:
try: try:
timer_tasks = self.__cfg_mgr.get(ConfigManager.ConfigType.TIMERTASK) timer_tasks = self.__cfg_mgr.get(CfgKey.TIMERTASK.ROOT)
if timer_tasks and "timer_tasks" in timer_tasks: if timer_tasks and "timer_tasks" in timer_tasks:
for task in timer_tasks["timer_tasks"]: for task in timer_tasks["timer_tasks"]:
task["added_time"] = datetime.strptime(task["added_time"], "%Y-%m-%d %H:%M:%S") task["added_time"] = datetime.strptime(task["added_time"], "%Y-%m-%d %H:%M:%S")
@@ -277,7 +279,7 @@ class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget):
if "repeat_history" in task: if "repeat_history" in task:
for item in task["repeat_history"]: for item in task["repeat_history"]:
item["result"] = item["result"].value item["result"] = item["result"].value
self.__cfg_mgr.set(ConfigManager.ConfigType.TIMERTASK, "", { "timer_tasks": timer_tasks }) self.__cfg_mgr.set(CfgKey.TIMERTASK.ROOT, { "timer_tasks": timer_tasks })
return True return True
except Exception as e: except Exception as e:
QMessageBox.warning( QMessageBox.warning(
@@ -437,7 +439,7 @@ class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget):
f"下次执行时间:{datetime.strftime(timer_task["execute_time"], "%Y-%m-%d %H:%M:%S")}\n" f"下次执行时间:{datetime.strftime(timer_task["execute_time"], "%Y-%m-%d %H:%M:%S")}\n"
f"已记录次数:{history_count}" f"已记录次数:{history_count}"
) )
def deleteTask( def deleteTask(
self, self,
+117
View File
@@ -0,0 +1,117 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
You may use, modify, and distribute this file under the terms of the MIT License.
See the LICENSE file for details.
"""
from dataclasses import dataclass
from enum import Enum
from typing import Any, Optional, Protocol
class ConfigType(Enum):
"""
Config type enum. Values represent the default filename.
"""
GLOBAL = "autolibrary.json"
BULLETIN = "bulletin.json"
TIMERTASK = "timer_task.json"
@dataclass(frozen=True)
class ConfigPath:
"""
A typed configuration path that carries both the config file
and the dot-separated key in a single object.
Consumers pass this directly to ConfigProvider.get/set,
eliminating the need to import ConfigType separately.
"""
config_type: ConfigType
key: str = ""
class CfgKey:
"""
Type-safe hierarchical configuration key constants.
Each leaf is a ConfigPath that can be passed directly to
``ConfigProvider.get()`` or ``ConfigProvider.set()``.
Usage::
CfgKey.GLOBAL.AUTOMATION.RUN_PATH.PATHS
# -> ConfigPath(ConfigType.GLOBAL, "automation.run_path.paths")
config.get(CfgKey.GLOBAL.AUTOMATION.RUN_PATH.PATHS, [])
config.set(CfgKey.GLOBAL.AUTOMATION.RUN_PATH.PATHS, value)
"""
class GLOBAL:
class AUTOMATION:
ROOT = ConfigPath(ConfigType.GLOBAL, "automation")
class RUN_PATH:
ROOT = ConfigPath(ConfigType.GLOBAL, "automation.run_path")
CURRENT = ConfigPath(ConfigType.GLOBAL, "automation.run_path.current")
PATHS = ConfigPath(ConfigType.GLOBAL, "automation.run_path.paths")
class USER_PATH:
ROOT = ConfigPath(ConfigType.GLOBAL, "automation.user_path")
CURRENT = ConfigPath(ConfigType.GLOBAL, "automation.user_path.current")
PATHS = ConfigPath(ConfigType.GLOBAL, "automation.user_path.paths")
class TIMERTASK:
ROOT = ConfigPath(ConfigType.TIMERTASK, "")
TIMER_TASKS = ConfigPath(ConfigType.TIMERTASK, "timer_tasks")
class BULLETIN:
ROOT = ConfigPath(ConfigType.BULLETIN, "")
BULLETIN = ConfigPath(ConfigType.BULLETIN, "bulletin")
LAST_SYNC_TIME = ConfigPath(ConfigType.BULLETIN, "last_sync_time")
class ConfigProvider(Protocol):
"""
Abstract interface for configuration storage access.
Concrete implementations (e.g. ConfigManager) conform to
this protocol structurally rather than through explicit
inheritance.
"""
def get(
self,
key: ConfigPath,
default: Optional[Any] = None
) -> Any:
"""
Retrieve a configuration value.
Args:
key: A ConfigPath object specifying which config file
and key to read from.
default: Fallback value if the key is not found.
Returns:
The configuration value at the given key path.
"""
...
def set(
self,
key: ConfigPath,
value: Any = None
) -> None:
"""
Set a configuration value and persist to disk.
Args:
key: A ConfigPath object specifying which config file
and key to write to.
value: The value to store.
"""
...
+11
View File
@@ -0,0 +1,11 @@
"""
Interfaces module for the AutoLibrary project.
Defines abstract interfaces (Protocols) and shared type definitions
used across layers to decouple consumers from concrete implementations.
Key components:
- ConfigProvider: Abstract interface for configuration access.
- ConfigType: Enumeration of configuration file types.
- ConfigKey: Type-safe hierarchical key constants for config lookups.
"""
+11 -22
View File
@@ -10,26 +10,17 @@ See the LICENSE file for details.
import os import os
import threading import threading
from enum import Enum
from typing import Any, Optional from typing import Any, Optional
from utils.JSONReader import JSONReader from utils.JSONReader import JSONReader
from utils.JSONWriter import JSONWriter from utils.JSONWriter import JSONWriter
from interfaces.ConfigProvider import ConfigType, ConfigPath
# This config manager class only responsible for global and other # This config manager class only responsible for global and other
# unconfigurable config files. # unconfigurable config files.
class ConfigType(Enum):
"""
Config type class. Values represent the default filename.
"""
GLOBAL = "autolibrary.json" # Global config file.
BULLETIN = "bulletin.json" # Bulletin board config file.
TIMERTASK = "timer_task.json" # Timer task config file.
class ConfigTemplate: class ConfigTemplate:
""" """
Config template class. Config template class.
@@ -120,16 +111,15 @@ class ConfigManager:
def get( def get(
self, self,
config_type: ConfigType, key: ConfigPath,
key: str = "",
default: Optional[Any] = None default: Optional[Any] = None
) -> Any: ) -> Any:
with self.__config_lock: with self.__config_lock:
config_data = self.__config_data[config_type.value] config_data = self.__config_data[key.config_type.value]
if key == "": if key.key == "":
return config_data return config_data
keys = key.split('.') keys = key.key.split('.')
for k in keys[:-1]: for k in keys[:-1]:
config_data = config_data.get(k, None) config_data = config_data.get(k, None)
if config_data is None: if config_data is None:
@@ -139,24 +129,23 @@ class ConfigManager:
def set( def set(
self, self,
config_type: ConfigType, key: ConfigPath,
key: str = "",
value: Any = None value: Any = None
): ):
with self.__config_lock: with self.__config_lock:
root_data = self.__config_data[config_type.value] root_data = self.__config_data[key.config_type.value]
if key == "": if key.key == "":
self.__config_data[config_type.value] = value self.__config_data[key.config_type.value] = value
else: else:
keys = key.split('.') keys = key.key.split('.')
config_data = root_data config_data = root_data
for k in keys[:-1]: for k in keys[:-1]:
if k not in config_data: if k not in config_data:
config_data[k] = {} config_data[k] = {}
config_data = config_data[k] config_data = config_data[k]
config_data[keys[-1]] = value config_data[keys[-1]] = value
self.save(config_type) self.save(key.config_type)
def save( def save(
+4 -2
View File
@@ -11,6 +11,8 @@ import os
import managers.config.ConfigManager as ConfigManager import managers.config.ConfigManager as ConfigManager
from interfaces.ConfigProvider import CfgKey
class ConfigUtils: class ConfigUtils:
""" """
Config utilities class. Config utilities class.
@@ -29,7 +31,7 @@ class ConfigUtils:
cfg_mgr = ConfigManager.instance() # config manager instance cfg_mgr = ConfigManager.instance() # config manager instance
config_paths = {"run": "", "user": ""} config_paths = {"run": "", "user": ""}
auto_config = cfg_mgr.get(ConfigManager.ConfigType.GLOBAL, "automation", {}) auto_config = cfg_mgr.get(CfgKey.GLOBAL.AUTOMATION.ROOT, {})
for cfg_type in ["run", "user"]: for cfg_type in ["run", "user"]:
paths = auto_config.get(f"{cfg_type}_path", {}).get("paths", []) paths = auto_config.get(f"{cfg_type}_path", {}).get("paths", [])
index = auto_config.get(f"{cfg_type}_path", {}).get("current", 0) index = auto_config.get(f"{cfg_type}_path", {}).get("current", 0)
@@ -42,5 +44,5 @@ class ConfigUtils:
config_paths[cfg_type] = paths[index] config_paths[cfg_type] = paths[index]
data = {"current": index, "paths": paths} data = {"current": index, "paths": paths}
auto_config[f"{cfg_type}_path"] = data auto_config[f"{cfg_type}_path"] = data
cfg_mgr.set(ConfigManager.ConfigType.GLOBAL, "automation", auto_config) cfg_mgr.set(CfgKey.GLOBAL.AUTOMATION.ROOT, auto_config)
return config_paths return config_paths