Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 56 additions & 4 deletions astrbot/core/config/astrbot_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,61 @@
logger = logging.getLogger("astrbot")


def _is_config_number(value) -> bool:
return isinstance(value, (int, float)) and not isinstance(value, bool)


_SCHEMA_TYPE_VALIDATORS = {
"int": lambda v: isinstance(v, int) and not isinstance(v, bool),
"float": _is_config_number,
"bool": lambda v: isinstance(v, bool),
"string": lambda v: isinstance(v, str),
"text": lambda v: isinstance(v, str),
"list": lambda v: isinstance(v, list),
"file": lambda v: isinstance(v, list),
"object": lambda v: isinstance(v, dict),
"dict": lambda v: isinstance(v, dict),
"template_list": lambda v: isinstance(v, list),
}


def _validate_schema_default(field: str, typ: str, default) -> None:
if not _SCHEMA_TYPE_VALIDATORS[typ](default):
raise TypeError(f"配置项 {field} 的 default 与类型 {typ} 不匹配")


def _validate_schema_slider(field: str, typ: str, slider: dict) -> None:
if typ not in ("int", "float"):
raise TypeError(f"配置项 {field} 只有 int/float 类型支持 slider")
if not isinstance(slider, dict) or not all(
_is_config_number(slider.get(key)) for key in ("min", "max", "step")
):
raise TypeError(
f"配置项 {field} 的 slider 必须包含数字 min/max/step",
)


def _validate_config_schema_item(field: str, item: dict) -> None:
typ = item["type"]
if typ not in DEFAULT_VALUE_MAP:
raise TypeError(
f"不受支持的配置类型 {typ}。支持的类型有:{DEFAULT_VALUE_MAP.keys()}",
)
if "options" in item and not isinstance(item["options"], list):
raise TypeError(f"配置项 {field} 的 options 必须是列表")
if "obvious_hint" in item and not isinstance(item["obvious_hint"], bool):
raise TypeError(f"配置项 {field} 的 obvious_hint 必须是布尔值")
if "slider" in item:
_validate_schema_slider(field, typ, item["slider"])
if typ == "object" and not isinstance(item.get("items"), dict):
raise TypeError(f"配置项 {field} 的 items 必须是对象")
default = item["default"] if "default" in item else DEFAULT_VALUE_MAP[typ]
_validate_schema_default(field, typ, default)
if typ == "object":
for child_key, child_item in item["items"].items():
_validate_config_schema_item(f"{field}.{child_key}", child_item)


class RateLimitStrategy(enum.Enum):
STALL = "stall"
DISCARD = "discard"
Expand Down Expand Up @@ -133,10 +188,7 @@ def _config_schema_to_default_config(self, schema: dict) -> dict:

def _parse_schema(schema: dict, conf: dict) -> None:
for k, v in schema.items():
if v["type"] not in DEFAULT_VALUE_MAP:
raise TypeError(
f"不受支持的配置类型 {v['type']}。支持的类型有:{DEFAULT_VALUE_MAP.keys()}",
)
_validate_config_schema_item(k, v)
if "default" in v:
default = v["default"]
else:
Expand Down
1 change: 1 addition & 0 deletions astrbot/core/config/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -4301,5 +4301,6 @@
"list": [],
"file": [],
"object": {},
"dict": {},
"template_list": [],
}
9 changes: 5 additions & 4 deletions docs/zh/dev/star/guides/plugin-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,15 @@ AstrBot 提供了“强大”的配置解析和可视化功能。能够让用户
}
```

- `type`: **此项必填**。配置的类型。支持 `string`, `text`, `int`, `float`, `bool`, `object`, `list`, `dict`, `template_list`。当类型为 `text` 时,将会可视化为一个更大的可拖拽宽高的 textarea 组件,以适应大文本。
- `type`: **此项必填**。配置的类型。支持 `string`, `text`, `int`, `float`, `bool`, `object`, `list`, `dict`, `file`, `template_list`。当类型为 `text` 时,将会可视化为一个更大的可拖拽宽高的 textarea 组件,以适应大文本。
- `description`: 可选。配置的描述。建议一句话描述配置的行为。
- `hint`: 可选。配置的提示信息,表现在上图中右边的问号按钮,当鼠标悬浮在问号按钮上时显示。
- `obvious_hint`: 可选。配置的 hint 是否醒目显示。如上图的 `token`。
- `default`: 可选。配置的默认值。如果用户没有配置,将使用默认值。int 是 0,float 是 0.0,bool 是 False,string 是 "",object 是 {},list 是 []。
- `obvious_hint`: 可选,布尔值。配置的 hint 是否醒目显示;只有同时配置了 `hint` 时才会显示。如上图的 `token`。
- `default`: 可选。配置的默认值。如果用户没有配置,将使用默认值。int 是 0,float 是 0.0,bool 是 False,string/text 是 "",object/dict 是 {},list/file/template_list 是 []。
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
- `items`: 可选。如果配置的类型是 `object`,需要添加 `items` 字段。`items` 的内容是这个配置项的子 Schema。理论上可以无限嵌套,但是不建议过多嵌套。
- `invisible`: 可选。配置是否隐藏。默认是 `false`。如果设置为 `true`,则不会在管理面板上显示。
- `options`: 可选。一个列表,如 `"options": ["chat", "agent", "workflow"]`。提供下拉列表可选项。
- `options`: 可选,必须是列表,如 `"options": ["chat", "agent", "workflow"]`。提供下拉列表可选项。
- `slider`: 可选,仅支持 `int` / `float` 类型。必须包含数字类型的 `min`、`max`、`step`。
- `editor_mode`: 可选。是否启用代码编辑器模式。需要 AstrBot >= `v3.5.10`, 低于这个版本不会报错,但不会生效。默认是 false。
- `editor_language`: 可选。代码编辑器的代码语言,默认为 `json`。
- `editor_theme`: 可选。代码编辑器的主题,可选值有 `vs-light`(默认), `vs-dark`。
Expand Down
40 changes: 40 additions & 0 deletions tests/unit/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,46 @@ def test_template_list_type(self, temp_config_path):

assert config.templates == []

def test_dict_schema_type(self, temp_config_path):
"""Test dict schema type."""
schema = {
"headers": {"type": "dict"},
}

config = AstrBotConfig(config_path=temp_config_path, schema=schema)

assert config.headers == {}

@pytest.mark.parametrize(
("schema", "error"),
[
(
{"field": {"type": "string", "default": 1}},
"default 与类型 string 不匹配",
),
(
{"field": {"type": "list", "options": "bad"}},
"options 必须是列表",
),
(
{"field": {"type": "string", "obvious_hint": "yes"}},
"obvious_hint 必须是布尔值",
),
(
{"field": {"type": "float", "slider": {"min": "0", "max": 1, "step": 1}}},
"slider 必须包含数字 min/max/step",
),
(
{"field": {"type": "string", "slider": {"min": 0, "max": 1, "step": 1}}},
"只有 int/float 类型支持 slider",
),
],
)
def test_schema_metadata_validation(self, temp_config_path, schema, error):
"""Test schema metadata validation."""
with pytest.raises(TypeError, match=error):
AstrBotConfig(config_path=temp_config_path, schema=schema)

def test_nested_object_schema(self, temp_config_path):
"""Test nested object schema conversion."""
schema = {
Expand Down
Loading