Source code for behave_kit.steps.suggestions
"""Better error messages: suggest similar steps for undefined step names.
Does not wrap or modify Behave's step registry. Reads step patterns from
`behave.step_registry.registry` (a public module-level singleton) and
compares them against the undefined step's text with `difflib`.
"""
from __future__ import annotations
import difflib
from collections.abc import Callable
from behave.step_registry import registry as the_step_registry
from behave_kit._core.logging import get_logger
from behave_kit._core.types import Context
logger = get_logger("steps.suggestions")
def _known_step_patterns() -> list[str]:
"""Collect every registered step pattern from Behave's step registry."""
patterns: list[str] = []
for matchers in the_step_registry.steps.values():
patterns.extend(matcher.pattern for matcher in matchers)
return patterns
[docs]
def suggest_for_undefined(step: object) -> list[str]:
"""Log the closest registered step pattern(s) for an undefined ``step``.
Returns the list of close matches found (possibly empty).
"""
step_name = str(getattr(step, "name", step))
matches = difflib.get_close_matches(step_name, _known_step_patterns())
if matches:
logger.info("Step '%s' has not been defined. Did you mean: '%s'?", step_name, matches[0])
return matches
[docs]
def setup_suggestions(context: Context) -> Callable[[Context, object], None]:
"""Return an ``after_step(context, step)`` hook that logs suggestions.
Wire it in ``environment.py``::
_suggest = behave_kit.steps.setup_suggestions(context)
def after_step(context, step):
_suggest(context, step)
"""
def after_step_hook(hook_context: Context, step: object) -> None:
"""Suggest similar steps when ``step`` is reported as undefined."""
status = getattr(step, "status", None)
status_name = str(getattr(status, "name", status)).lower()
if status_name == "undefined":
suggest_for_undefined(step)
return after_step_hook