fix: validate plugin config schema metadata#8257
Open
he-yufeng wants to merge 1 commit into
Open
Conversation
Contributor
There was a problem hiding this comment.
Hey - I've found 2 issues, and left some high level feedback:
- The
validatorsmapping inside_validate_defaultis rebuilt on every call and must be kept manually in sync withDEFAULT_VALUE_MAP; consider centralizing type metadata (e.g., a single shared registry of type → default/validator) to avoid divergence and reduce overhead. - The
_is_numberhelper usesisinstance(value, int | float), which relies on the 3.10+ union syntax inisinstance; if this module is intended to run on earlier Python versions, switch toisinstance(value, (int, float))to keep compatibility.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The `validators` mapping inside `_validate_default` is rebuilt on every call and must be kept manually in sync with `DEFAULT_VALUE_MAP`; consider centralizing type metadata (e.g., a single shared registry of type → default/validator) to avoid divergence and reduce overhead.
- The `_is_number` helper uses `isinstance(value, int | float)`, which relies on the 3.10+ union syntax in `isinstance`; if this module is intended to run on earlier Python versions, switch to `isinstance(value, (int, float))` to keep compatibility.
## Individual Comments
### Comment 1
<location path="docs/zh/dev/star/guides/plugin-config.md" line_range="50" />
<code_context>
-- `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 是 []。
- `items`: 可选。如果配置的类型是 `object`,需要添加 `items` 字段。`items` 的内容是这个配置项的子 Schema。理论上可以无限嵌套,但是不建议过多嵌套。
- `invisible`: 可选。配置是否隐藏。默认是 `false`。如果设置为 `true`,则不会在管理面板上显示。
</code_context>
<issue_to_address>
**question:** The mention of `file` in `list/file/template_list` seems inconsistent with the supported types listed above.
In the `type` description, the supported values are `string`, `text`, `int`, `float`, `bool`, `object`, `list`, `dict`, `template_list`, but the `default` description additionally mentions `file` (in `list/file/template_list`). Please either add `file` to the supported `type` list (if valid) or remove it from the `default` description to keep the docs consistent.
</issue_to_address>
### Comment 2
<location path="astrbot/core/config/astrbot_config.py" line_range="134" />
<code_context>
- def _parse_schema(schema: dict, conf: dict) -> None:
- for k, v in schema.items():
- if v["type"] not in DEFAULT_VALUE_MAP:
+ def _is_number(value) -> bool:
+ return isinstance(value, int | float) and not isinstance(value, bool)
+
</code_context>
<issue_to_address>
**issue (complexity):** Consider extracting the new validation helpers and splitting them into smaller top-level functions so `_config_schema_to_default_config` stays focused on building the config structure.
You can keep all the new validation but reduce complexity by:
1. **Lifting helpers out of `_config_schema_to_default_config`**
2. **Centralizing type → validator mapping instead of rebuilding `validators` inline**
3. **Splitting the object‑specific validation into a separate helper to flatten `_validate_schema_item`**
### 1. Move helpers to private methods / module helpers
Instead of defining `_is_number`, `_validate_default`, and `_validate_schema_item` as inner functions, lift them out. That makes `_config_schema_to_default_config` easier to scan and keeps helpers testable.
```python
# module or class level
def _is_number(value) -> bool:
return isinstance(value, (int, float)) and not isinstance(value, bool)
TYPE_VALIDATORS: dict[str, Callable[[Any], bool]] = {
"int": lambda v: isinstance(v, int) and not isinstance(v, bool),
"float": _is_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),
"template_list": lambda v: isinstance(v, list),
"object": lambda v: isinstance(v, dict),
"dict": lambda v: isinstance(v, dict),
}
def _validate_default(field: str, typ: str, default) -> None:
validator = TYPE_VALIDATORS.get(typ)
if not validator:
raise TypeError(
f"不受支持的配置类型 {typ}。支持的类型有:{DEFAULT_VALUE_MAP.keys()}",
)
if not validator(default):
raise TypeError(f"配置项 {field} 的 default 与类型 {typ} 不匹配")
```
Then your method reduces to wiring these helpers:
```python
def _config_schema_to_default_config(self, schema: dict) -> dict:
conf: dict = {}
def _parse_schema(schema: dict, conf: dict) -> None:
for k, v in schema.items():
self._validate_schema_item(k, v) # or module-level function
default = v["default"] if "default" in v else DEFAULT_VALUE_MAP[v["type"]]
if v["type"] == "object":
conf[k] = {}
_parse_schema(v["items"], conf[k])
elif v["type"] == "template_list":
conf[k] = default
else:
conf[k] = default
_parse_schema(schema, conf)
return conf
```
(You can keep `_parse_schema` inner if you want, but it’s now focused only on building `conf`.)
### 2. Split `_validate_schema_item` into small helpers
This reduces nesting and keeps each concern local.
```python
def _validate_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_number(slider.get(key)) for key in ("min", "max", "step")
):
raise TypeError(f"配置项 {field} 的 slider 必须包含数字 min/max/step")
def _validate_object_items(field: str, item: dict) -> None:
if not isinstance(item.get("items"), dict):
raise TypeError(f"配置项 {field} 的 items 必须是对象")
for child_key, child_item in item["items"].items():
_validate_schema_item(f"{field}.{child_key}", child_item)
```
Then `_validate_schema_item` becomes flatter:
```python
def _validate_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_slider(field, typ, item["slider"])
default = item["default"] if "default" in item else DEFAULT_VALUE_MAP[typ]
_validate_default(field, typ, default)
if typ == "object":
_validate_object_items(field, item)
```
This retains your validation behavior but removes nested inner definitions, centralizes the type/validator logic, and keeps `_config_schema_to_default_config` focused on its primary job.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Contributor
There was a problem hiding this comment.
Code Review
This pull request introduces comprehensive schema validation for configuration items in AstrBotConfig. It adds internal validation logic to ensure that default values, options, and slider configurations match their declared types (e.g., int, float, bool, list). Additionally, it adds support for a dict type, updates the documentation to reflect these metadata requirements, and includes unit tests to verify the new validation rules. I have no feedback to provide.
987fd81 to
270d48e
Compare
Contributor
Author
|
Updated to address the review feedback:
Validation:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #8252.
This tightens plugin
_conf_schema.jsonhandling so invalid metadata is rejected before it reaches the WebUI:dictto the default value map so the documented schema type worksdefaultvalues against their declared typeoptionsto be a listobvious_hintto be booleansliderto be used only onint/floatfields with numericmin/max/stepTo verify
python -m pytest tests/unit/test_config.py -q -p no:cacheproviderpython -m ruff check astrbot/core/config/astrbot_config.py astrbot/core/config/default.py tests/unit/test_config.pypython -m py_compile astrbot/core/config/astrbot_config.py astrbot/core/config/default.py tests/unit/test_config.pygit diff --checkSummary by Sourcery
Validate plugin configuration schema metadata and document the supported constraints.
New Features:
Bug Fixes:
Enhancements:
Documentation:
Tests: