Source code for mllm_shap.plugins.options
"""Lookup and validation of ``explanation.plugin_options``.
@domain plugins
@see [[knowledge/architecture/plugins]]
"""
from collections.abc import Mapping
from typing import TYPE_CHECKING
from pydantic import ValidationError
from ..errors import ConfigError
from ..protocols.plugin import DeclaresPluginOptions
from .groups import PluginGroup
if TYPE_CHECKING:
from ..pipeline.config import ExplainConfig
from .registry import PluginFactory, PluginRegistry
[docs]
def group_key(group: PluginGroup | str) -> str:
"""Return the short group name used in qualified option keys (``value_fns``)."""
return str(group).removeprefix("mllm_shap.")
[docs]
def select_plugin_options(
plugin_options: Mapping[str, Mapping[str, object]],
group: PluginGroup | str,
name: str,
) -> Mapping[str, object]:
"""Return the options stored under ``<group>/<name>`` or the bare ``<name>``.
Raises:
ConfigError: If both keys are present.
"""
qualified = f"{group_key(group)}/{name}"
if qualified in plugin_options and name in plugin_options:
msg = (
f"plugin_options has both {qualified!r} and {name!r} for the same "
"plugin; keep one"
)
raise ConfigError(msg)
return plugin_options.get(qualified, plugin_options.get(name, {}))
[docs]
def validate_plugin_options(
factory: "PluginFactory",
group: PluginGroup | str,
name: str,
options: Mapping[str, object],
) -> Mapping[str, object]:
"""Check ``options`` against the factory's declared options model.
Factories that declare no :class:`~mllm_shap.protocols.plugin.PluginOptions`
model get ``options`` back unchanged.
Raises:
ConfigError: Naming the group, plugin and key of an unknown option or of
a value with the wrong type.
"""
if not isinstance(factory, DeclaresPluginOptions):
return options
try:
validated = factory.options_model.model_validate(dict(options))
except ValidationError as error:
problems = "; ".join(
f"{'.'.join(str(part) for part in detail['loc'])!r}: {detail['msg']}"
for detail in error.errors()
)
msg = f"invalid plugin_options for {group_key(group)}/{name}: {problems}"
raise ConfigError(msg) from error
return validated.model_dump(exclude_unset=True)
[docs]
def run_plugins(config: "ExplainConfig") -> dict[str, str]:
"""Return the plugin name the run selects in each group, keyed by short group."""
explanation = config.explanation
return {
group_key(PluginGroup.ESTIMATORS): explanation.estimator,
group_key(PluginGroup.ESTIMANDS): explanation.estimand.value,
group_key(PluginGroup.VALUE_FNS): explanation.value_fn,
group_key(PluginGroup.NORMALIZERS): explanation.normalizer,
group_key(PluginGroup.BACKENDS): config.backend.kind,
group_key(PluginGroup.PARTITIONS): explanation.players,
group_key(PluginGroup.ABSENCE_POLICIES): explanation.absence_policy,
}
[docs]
def check_plugin_options(config: "ExplainConfig", registry: "PluginRegistry") -> None:
"""Check every ``explanation.plugin_options`` entry before a run starts.
A key is ``<group>/<name>`` (for example ``absence_policies/pad``) or a bare
plugin name. It must name a plugin the run uses, or a registered renderer;
a bare name must match one group only. Options of plugins that declare an
options model are validated against it.
Raises:
ConfigError: If a key names no plugin of the run, an unknown group or
more than one plugin, or holds an unknown option.
"""
selected = run_plugins(config)
renderers = set(registry.names(PluginGroup.RENDERERS))
renderer_group = group_key(PluginGroup.RENDERERS)
for key, options in config.explanation.plugin_options.items():
if "/" in key:
group, _, name = key.partition("/")
if group == renderer_group:
matched = name in renderers
elif group in selected:
matched = selected[group] == name
else:
msg = (
f"plugin_options key {key!r} names unknown group {group!r}; "
f"use one of {sorted([*selected, renderer_group])}"
)
raise ConfigError(msg)
if not matched:
msg = f"plugin_options key {key!r} matches no plugin of this run"
raise ConfigError(msg)
groups = [group]
else:
name = key
groups = [group for group, chosen in selected.items() if chosen == name]
if name in renderers:
groups.append(renderer_group)
if not groups:
msg = (
f"plugin_options key {key!r} matches no plugin of this run; "
f"selected plugins: {selected}"
)
raise ConfigError(msg)
if len(groups) > 1:
msg = (
f"plugin_options key {key!r} is ambiguous (groups {groups}); "
f"use '<group>/{name}'"
)
raise ConfigError(msg)
group = groups[0]
full_group = f"mllm_shap.{group}"
select_plugin_options(config.explanation.plugin_options, full_group, name)
validate_plugin_options(
registry.resolve_factory(full_group, name), full_group, name, options
)