Skip to content

Commit 80e42db

Browse files
committed
fix compat
1 parent 3490249 commit 80e42db

7 files changed

Lines changed: 52 additions & 32 deletions

File tree

nonebot_plugin_picmenu_next/data_source/collect.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,11 @@ async def get_info_from_plugin(plugin: Plugin) -> PMNPluginInfo:
6060
extra: PMNPluginExtra | None = None
6161
if meta:
6262
with warning_suppress(f"Failed to parse plugin metadata of {plugin.id_}"):
63-
extra = type_validate_python(PMNPluginExtra, meta.extra)
63+
metadata_extra = {
64+
kl if (kl := key.lower()) == "author" else key: value
65+
for key, value in meta.extra.items()
66+
}
67+
extra = type_validate_python(PMNPluginExtra, metadata_extra)
6468

6569
name = normalize_plugin_name(meta.name if meta else plugin.id_)
6670

nonebot_plugin_picmenu_next/data_source/mixin.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
from collections import defaultdict
2-
from collections.abc import Callable, Coroutine, Sequence
2+
from collections.abc import Coroutine, Sequence
33
from dataclasses import dataclass
4-
from typing import Any, Concatenate, Generic, TypeAlias, TypeVar
4+
from typing import ( # noqa: UP035 - Python 3.10 compat
5+
Any,
6+
Callable,
7+
Concatenate,
8+
Generic,
9+
TypeAlias,
10+
TypeVar,
11+
)
512
from typing_extensions import ParamSpec
613

714
from cookit import DecoListCollector

nonebot_plugin_picmenu_next/data_source/models.py

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
model_fields_set,
99
model_validator,
1010
model_with_model_config,
11-
type_dump_python,
1211
)
1312
from nonebot import get_plugin
1413
from nonebot.plugin import Plugin
@@ -96,19 +95,6 @@ class PMNPluginExtra(CompatModel):
9695
menu_data: list[PMDataItem] | None = None
9796
pmn: PMNData | None = None
9897

99-
@model_validator(mode="before")
100-
def normalize_input(cls, values: Any): # noqa: N805
101-
if isinstance(values, PMNPluginExtra):
102-
values = type_dump_python(values, exclude_unset=True)
103-
if not isinstance(values, dict):
104-
raise TypeError(f"Expected dict, got {type(values)}")
105-
should_normalize_keys = {x for x in values if x.lower() == "author"}
106-
for key in should_normalize_keys:
107-
value = values[key]
108-
del values[key]
109-
values[key.lower()] = value
110-
return values
111-
11298

11399
class OptionalPMNPluginInfo(CompatModel):
114100
name: str | None = None
@@ -175,12 +161,14 @@ class ExternalPluginInfo(CompatModel):
175161
pmn: ExternalPMNData = ExternalPMNData()
176162
supported_adapters: set[str] | None = None
177163

178-
@model_validator(mode="after")
179-
def normalize_input(cls, values: dict[str, Any]): # noqa: N805
164+
@model_validator(mode="before")
165+
def normalize_input(cls, values: Any): # noqa: N805
180166
# these params cannot be explicitly none
167+
if not isinstance(values, dict):
168+
return values
181169
for key in ("name", "funcs"):
182170
if key in values and values[key] is None:
183-
raise TypeError(f"`{key}` cannot be null")
171+
raise ValueError(f"`{key}` cannot be null")
184172
return values
185173

186174
def has_func_override(self) -> bool:

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ dependencies = [
77
"nonebot2>=2.5.0",
88
"nonebot-plugin-alconna>=0.62.1",
99
"nonebot-plugin-localstore>=0.7.4",
10-
"cookit[jinja,loguru,nonebot-alconna,nonebot-localstore,pw,pyd]>=0.13.3",
10+
"cookit[jinja,loguru,nonebot-alconna,nonebot-localstore,pw,pyd]>=0.14.0",
1111
"pypinyin>=0.55.0",
1212
"jieba>=0.42.1",
1313
"thefuzz>=0.22.1",

tests_nbp_picmenu_next/data_source/test_collect.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,26 @@ async def test_plugin_metadata_falls_back_to_distribution_version_and_author(
136136
assert info.author == "Alice"
137137

138138

139+
async def test_plugin_metadata_normalizes_picmenu_extra_at_collection_boundary(
140+
picmenu_plugin: object,
141+
) -> None:
142+
"""Collection accepts the legacy capitalized author key in metadata extras."""
143+
from nonebot_plugin_picmenu_next.data_source import collect
144+
145+
metadata = PluginMetadata(
146+
name="Capitalized Author",
147+
description="description",
148+
usage="usage",
149+
extra={"Author": "Alice"},
150+
)
151+
152+
info = await collect.get_info_from_plugin(
153+
make_plugin("capitalized_author", metadata),
154+
)
155+
156+
assert info.author == "Alice"
157+
158+
139159
def test_collect_menus_loads_yaml_and_applies_or_appends_external_infos(
140160
picmenu_plugin: object,
141161
monkeypatch: "pytest.MonkeyPatch",
@@ -249,7 +269,7 @@ def test_external_config_rejects_disallowed_nulls(
249269
from pydantic import ValidationError
250270

251271
for key in ("name", "funcs"):
252-
with pytest.raises(TypeError, match=f"`{key}` cannot be null"):
272+
with pytest.raises(ValidationError, match=f"`{key}` cannot be null"):
253273
type_validate_python(ExternalPluginInfo, {key: None})
254274

255275
with pytest.raises(ValidationError):

tests_nbp_picmenu_next/data_source/test_mixin.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@
66
import pytest
77

88

9+
def test_mixin_type_aliases_resolve_at_runtime(picmenu_plugin: object) -> None:
10+
"""Mixin extension type aliases resolve while the plugin data source loads."""
11+
from nonebot_plugin_picmenu_next.data_source import mixin
12+
13+
assert mixin.PluginCollectMixin is not None
14+
assert mixin.SelfMixin is not None
15+
16+
917
def test_self_mixin_registration_requires_an_owning_plugin(
1018
picmenu_plugin: object,
1119
monkeypatch: "pytest.MonkeyPatch",

tests_nbp_picmenu_next/data_source/test_models.py

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -98,23 +98,16 @@ def test_external_pmn_conversion_preserves_only_declared_display_options(
9898
assert converted.alc_force_enable_detect is False
9999

100100

101-
def test_model_validators_accept_model_instances_and_reject_non_mapping_input(
101+
def test_external_model_validator_rejects_non_mapping_input(
102102
picmenu_plugin: object,
103103
) -> None:
104-
"""Both external schemas normalize model input but reject scalar configuration."""
104+
"""External configuration rejects scalar input before field validation."""
105105
from cookit.pyd import type_validate_python
106-
from nonebot_plugin_picmenu_next.data_source.models import (
107-
ExternalPluginInfo,
108-
PMNPluginExtra,
109-
)
106+
from nonebot_plugin_picmenu_next.data_source.models import ExternalPluginInfo
110107

111-
extra = PMNPluginExtra(author="Alice")
112108
external = ExternalPluginInfo(name="External")
113109

114-
assert type_validate_python(PMNPluginExtra, extra).author == "Alice"
115110
assert type_validate_python(ExternalPluginInfo, external).name == "External"
116-
with pytest.raises(TypeError, match="Expected dict"):
117-
type_validate_python(PMNPluginExtra, 1)
118111
with pytest.raises(ValidationError):
119112
type_validate_python(ExternalPluginInfo, 1)
120113

0 commit comments

Comments
 (0)