docling-project/docling · error · ValueError

{opt_type.kind!r} already registered to class {self._classes

Error message

{opt_type.kind!r} already registered to class {self._classes[opt_type]!r}

What it means

Raised as ValueError by BaseFactory.register when a plugin tries to register an options type that is already mapped to a class. The factory enforces one implementation class per options type to keep resolution deterministic.

Source

Thrown at docling/models/factories/base_factory.py:81

            if opt_cls.kind == kind:
                return opt_cls(*args, **kwargs)
        raise RuntimeError(self._err_msg_on_class_not_found(kind))

    def _err_msg_on_class_not_found(self, kind: str):
        msg = []

        for opt, cls in self._classes.items():
            msg.append(f"\t{opt.kind!r} => {cls!r}")

        msg_str = "\n".join(msg)

        return f"No class found with the name {kind!r}, known classes are:\n{msg_str}"

    def register(self, cls: Type[A], plugin_name: str, plugin_module_name: str):
        opt_type = cls.get_options_type()

        if opt_type in self._classes:
            raise ValueError(
                f"{opt_type.kind!r} already registered to class {self._classes[opt_type]!r}"
            )

        self._classes[opt_type] = cls
        self._meta[opt_type] = FactoryMeta(
            kind=opt_type.kind, plugin_name=plugin_name, module=plugin_module_name
        )

    def load_from_plugins(
        self, plugin_name: Optional[str] = None, allow_external_plugins: bool = False
    ):
        plugin_name = plugin_name or self.plugin_name

        plugin_manager = PluginManager(plugin_name)
        plugin_manager.load_setuptools_entrypoints(plugin_name)

        for plugin_name, plugin_module in plugin_manager.list_name_plugin():
            plugin_module_name = str(plugin_module.__name__)  # type: ignore

View on GitHub (pinned to 61d76f1ff3)

Solutions

  1. Make the plugin idempotent: check `opt_type in factory.classes` before calling register, or guard registration behind a module-level flag.
  2. Give your plugin its own distinct options subclass with a unique kind instead of reusing an existing options class.
  3. Remove the duplicate import/registration path (e.g. registering both via entry point and via explicit import).

Example fix

# before
class MyModel(BaseModel):
    ...
factory.register(MyModel, 'my_plugin', 'my_plugin.models')  # ValueError if re-imported

# after
if MyModel.get_options_type() not in factory.classes:
    factory.register(MyModel, 'my_plugin', 'my_plugin.models')
Defensive patterns

Strategy: validation

Validate before calling

opt_type = MyModel.get_options_type()
if opt_type in factory.classes:
    print(f'skip: {opt_type.kind!r} already registered to {factory.classes[opt_type]!r}')
else:
    factory.register(MyModel, 'my_plugin', 'my_plugin.models')

Type guard

def needs_registration(factory, cls) -> bool:
    return cls.get_options_type() not in factory.classes

Try / catch

try:
    factory.register(cls, plugin_name, module_name)
except ValueError as e:
    if 'already registered' in str(e):
        pass  # idempotent plugin load
    else:
        raise

Prevention

When it happens

Trigger: Registering two classes whose get_options_type() returns the same options class — e.g. a plugin re-registers a class already registered by docling-core, or two plugins vendored/duplicated copies of the same options class.

Common situations: Developing a plugin that subclasses or re-imports an existing options class and calls register() again; double import of a plugin under different module names; running registration both at import time and explicitly.

Related errors


AI-assisted analysis of docling-project/docling@61d76f1ff3 (2026-08-14). Data as JSON: /api/errors/741d0df8824a6a37. Report an issue: GitHub.