p-e-w/heretic · error · TypeError

{cls.__name__} must not define __init__(). Use an optional i

Error message

{cls.__name__} must not define __init__(). Use an optional init(ctx) method for plugin-specific initialization.

What it means

Plugin subclasses are forbidden from defining their own __init__. The Plugin metaclass/base class handles construction itself, and plugin-specific initialization must go through an optional init(ctx) hook instead. This keeps plugin lifecycle uniform across the framework.

Source

Thrown at src/heretic/plugin.py:242

            if not isinstance(settings, settings_model):
                raise TypeError(
                    f"{self.__class__.__name__}.settings must be an instance of "
                    f"{settings_model.__name__}"
                )
        self.settings = settings
        self.heretic_settings = heretic_settings

    @classmethod
    def validate_contract(cls) -> None:
        """
        Validate the plugin contract.

        - Plugins must not define a constructor (`__init__`). Initialization is
          handled by `Plugin.__init__` and an optional `init(ctx)` method.
        - Plugin subclasses may define `settings: <BaseModelSubclass>` to declare a settings schema.
        """
        if "__init__" in cls.__dict__:
            raise TypeError(
                f"{cls.__name__} must not define __init__(). "
                "Use an optional init(ctx) method for plugin-specific initialization."
            )

    @classmethod
    def get_settings_model(cls) -> type[BaseModel] | None:
        """
        Return the plugin settings model, if present.
        - If the plugin has a `settings: <BaseModelSubclass>` type annotation,
          that type is used as the settings schema.
        - Otherwise: no settings schema.
        """

        def unwrap_settings_type(tp: Any) -> Any:
            """Unwrap `Annotated[T, ...]`."""
            while True:
                origin = get_origin(tp)
                if origin is Annotated:

View on GitHub (pinned to bedb94ef11)

Solutions

  1. Remove the __init__ method from the plugin class.
  2. Move initialization logic into an optional `init(self, ctx)` method.
  3. Pass any needed configuration via the plugin's `settings` (a pydantic BaseModel) rather than constructor arguments.

Example fix

// before
class MyScorer(Plugin):
    def __init__(self, model):
        self.model = model
// after
class MyScorer(Plugin):
    settings: MySettings
    def init(self, ctx):
        self.model = load_model(self.settings.model_name)
Defensive patterns

Strategy: validation

Validate before calling

import inspect
assert "__init__" not in MyPlugin.__dict__, "Plugin must not define __init__; use init(ctx)"

Try / catch

try:
    Plugin.validate_contract(MyPlugin)
except TypeError as e:
    if "must not define __init__" in str(e):
        print(f"Fix plugin {MyPlugin.__name__}: move init logic to init(ctx)")
    else:
        raise

Prevention

When it happens

Trigger: Defining a plugin class (subclass of the heretic Plugin base) with an `__init__` method in its class body; validate_contract is invoked via _load_and_init_scorers when plugins are loaded.

Common situations: Porting a scorer class written as a plain class into a heretic plugin while keeping its old constructor; copying a pydantic model-style class that relies on custom __init__ logic.

Related errors


AI-assisted analysis of p-e-w/heretic@bedb94ef11 (2026-08-29). Data as JSON: /api/errors/89e4c6336b2514e1. Report an issue: GitHub.