p-e-w/heretic · error · TypeError
{cls.__name__}.settings must be annotated with a pydantic.Ba
Error message
{cls.__name__}.settings must be annotated with a pydantic.BaseModel subclass What it means
A plugin's `settings` attribute must be annotated with a subclass of pydantic BaseModel. Any other annotation (a plain class, a non-model type, a primitive, or a non-type like a string forward reference resolving to something else) is rejected. This is raised in get_settings_model when unwrap_settings_type does not yield a BaseModel subclass.
Source
Thrown at src/heretic/plugin.py:278
if origin is Annotated:
tp = get_args(tp)[0]
continue
return tp
hints = get_type_hints(cls, include_extras=True)
annotated = hints.get("settings")
if annotated is None:
return None
model = unwrap_settings_type(annotated)
origin = get_origin(model)
if origin in (Union, types.UnionType) and type(None) in get_args(model):
raise TypeError(
f"{cls.__name__}.settings must not be Optional; "
"use a non-optional pydantic.BaseModel subclass (e.g. `settings: Settings`)."
)
if not isinstance(model, type) or not issubclass(model, BaseModel):
raise TypeError(
f"{cls.__name__}.settings must be annotated with a pydantic.BaseModel subclass"
)
return model
@classmethod
def validate_settings(
cls, raw_namespace: dict[str, Any] | None
) -> BaseModel | None:
"""
Validates plugin settings for this plugin class.
- If a settings model is present: returns an instance of that model.
- Otherwise returns None.
"""
settings_model = cls.get_settings_model()
if settings_model is None:
return None
return settings_model.model_validate(raw_namespace or {})View on GitHub (pinned to bedb94ef11)
Solutions
- Define a `class MySettings(BaseModel)` and annotate `settings: MySettings`.
- Ensure pydantic is imported and the annotation is a real class, not a generic or dataclass.
- Move defaults/validators into the pydantic model definition.
Example fix
// before
class MyScorer(Plugin):
settings: dict
// after
class MySettings(pydantic.BaseModel):
model_name: str
class MyScorer(Plugin):
settings: MySettings Defensive patterns
Strategy: validation
Validate before calling
from pydantic import BaseModel assert issubclass(MyPluginSettings, BaseModel), "settings model must subclass pydantic.BaseModel"
Type guard
import typing
from pydantic import BaseModel
def has_pydantic_settings(cls) -> bool:
ann = typing.get_type_hints(cls).get("settings")
return isinstance(ann, type) and issubclass(ann, BaseModel) Try / catch
try:
Plugin.get_settings_model(MyPlugin)
except TypeError as e:
if "must be annotated with a pydantic.BaseModel subclass" in str(e):
print(f"Fix {MyPlugin.__name__}.settings annotation")
else:
raise Prevention
- Always define settings as a pydantic BaseModel subclass.
- Don't annotate settings with dict/dataclass/TypedDict.
- Test plugin loading in CI to catch annotation mistakes early.
When it happens
Trigger: Declaring `settings: dict`, `settings: SomeDataclass`, `settings: int`, or any non-pydantic type on a Plugin subclass; triggered via get_settings_model from plugin __init__, _get_scorer_settings_raw, or validate_settings.
Common situations: Using a dataclass or TypedDict for settings out of habit; forgetting to import BaseModel and subclass it; annotating with a generic container type instead of a settings model.
Related errors
- {cls.__name__}.settings must not be Optional; use a non-opti
- Plugin '{name}' does not export a class named '{class_name}'
- You must append the plugin class name to the filepath like t
- File-based plugin must use the form 'path/to/plugin.py:Class
- Could not load plugin '{name}' (invalid module spec)
AI-assisted analysis of p-e-w/heretic@bedb94ef11 (2026-08-29).
Data as JSON: /api/errors/424157f31ff62f6c.
Report an issue: GitHub.