Skip to content

Python API

behave-gen exposes a programmatic API for embedding in custom tooling, IDE plugins, or CI integrations.

Project scaffolding

behave_gen.commands.init.init_project

init_project(target_dir: str | Path, options: InitOptions, registry: TemplateRegistry | None = None) -> Path

Create a new Behave project at target_dir/options.name.

Parameters:

Name Type Description Default
target_dir str | Path

Parent directory where the project folder is created.

required
options InitOptions

Init options.

required
registry TemplateRegistry | None

Optional template registry; defaults to the built-in one.

None

Returns:

Type Description
Path

The resolved path to the created project root.

Raises:

Type Description
InitError

If the target exists and force is not set, the template is unknown, the project name is invalid, or rendering fails.

Source code in behave_gen/commands/init.py
def init_project(  # noqa: PLR0912 - force overwrite has many filesystem-state branches.
    target_dir: str | Path,
    options: InitOptions,
    registry: TemplateRegistry | None = None,
) -> Path:
    """Create a new Behave project at ``target_dir/options.name``.

    Args:
        target_dir: Parent directory where the project folder is created.
        options: Init options.
        registry: Optional template registry; defaults to the built-in one.

    Returns:
        The resolved path to the created project root.

    Raises:
        InitError: If the target exists and ``force`` is not set, the template
            is unknown, the project name is invalid, or rendering fails.

    """
    try:
        name = validate_name(options.name)
    except ValueError as exc:
        raise InitError(f"Invalid project name: {exc}") from exc

    parent = Path(target_dir).resolve()
    if parent.exists() and not parent.is_dir():
        raise InitError(f"Target path exists but is not a directory: {parent}")
    try:
        parent.mkdir(parents=True, exist_ok=True)
    except OSError as exc:
        raise InitError(f"Could not create target directory {parent}: {exc}") from exc

    project_root = parent / name

    if project_root.exists() or project_root.is_symlink():
        if not options.force:
            raise InitError(f"Directory already exists: {project_root}. Use --force to overwrite.")
        try:
            if project_root.is_symlink():
                if project_root.is_dir() and sys.platform == "win32":
                    project_root.rmdir()
                else:
                    project_root.unlink()
            elif project_root.is_file():
                project_root.unlink()
            else:
                shutil.rmtree(project_root)
        except OSError as exc:
            raise InitError(f"Could not remove existing project {project_root}: {exc}") from exc

    try:
        project_root.mkdir(parents=True)
    except OSError as exc:
        raise InitError(f"Could not create project directory {project_root}: {exc}") from exc

    reg = registry if registry is not None else default_registry()
    try:
        template_set = reg.get(options.template)
    except KeyError as exc:
        raise InitError(str(exc)) from exc

    try:
        engine = get_engine(options.template_engine)
    except ValueError as exc:
        raise InitError(str(exc)) from exc

    skip, rename = build_skip_and_rename(options.kit, options.data)
    context = {"project_name": name, "name": name}
    try:
        template_set.render_to(project_root, context, engine, skip=skip, rename=rename)
    except TemplateRenderError as exc:
        raise InitError(str(exc)) from exc
    return project_root

behave_gen.commands.init.InitOptions dataclass

Options for the init command.

Source code in behave_gen/commands/init.py
@dataclass(frozen=True, slots=True)
class InitOptions:
    """Options for the init command."""

    name: str
    template: str = "default"
    kit: bool = False
    data: bool = False
    force: bool = False
    template_engine: str = "string"

Add feature

behave_gen.commands.add.add_feature

add_feature(project_root: str | Path, options: AddFeatureOptions, *, features_dir: str | Path = 'features', default_tags: tuple[str, ...] = ()) -> Path

Generate a .feature file inside project_root.

Parameters:

Name Type Description Default
project_root str | Path

Root of the Behave project.

required
options AddFeatureOptions

Add-feature options.

required
features_dir str | Path

Features directory relative to project_root.

'features'
default_tags tuple[str, ...]

Default tags from project configuration, merged with tags supplied on options.

()

Returns:

Type Description
Path

The path to the generated feature file.

Raises:

Type Description
AddError

If the project/features dir is missing, the template is unknown, the name is invalid, or the generated file fails to parse.

Source code in behave_gen/commands/add.py
def add_feature(
    project_root: str | Path,
    options: AddFeatureOptions,
    *,
    features_dir: str | Path = "features",
    default_tags: tuple[str, ...] = (),
) -> Path:
    """Generate a ``.feature`` file inside ``project_root``.

    Args:
        project_root: Root of the Behave project.
        options: Add-feature options.
        features_dir: Features directory relative to ``project_root``.
        default_tags: Default tags from project configuration, merged with
            tags supplied on ``options``.

    Returns:
        The path to the generated feature file.

    Raises:
        AddError: If the project/features dir is missing, the template is
            unknown, the name is invalid, or the generated file fails to parse.

    """
    try:
        name = validate_name(options.name)
    except ValueError as exc:
        raise AddError(f"Invalid feature name: {exc}") from exc

    root = Path(project_root).resolve()
    if not root.is_dir():
        raise AddError(f"Project root not found: {root}")

    features = (root / features_dir).resolve()
    if not features.is_relative_to(root):
        raise AddError(f"Features directory {features} escapes project root {root}.")
    try:
        features.mkdir(parents=True, exist_ok=True)
    except OSError as exc:
        raise AddError(f"Could not create features directory {features}: {exc}") from exc
    target = features / f"{name}.feature"
    if not target.is_relative_to(features):
        raise AddError(f"Feature file {target} must be inside features directory {features}.")
    if target.exists() or target.is_symlink():
        raise AddError(f"Feature file already exists: {target}. Use a different name or remove it.")

    raw = _load_feature_template(options.template)
    all_tags = default_tags + _tag_parts(options.tags)
    context = {
        "feature_name": _humanize(name),
        "name": name,
        "tags": _format_tag_line(all_tags),
    }
    try:
        rendered = string.Template(raw).substitute(context)
    except KeyError as exc:
        key = exc.args[0] if exc.args else "<unknown>"
        raise AddError(f"Missing template variable ${key}.") from exc

    # Validate the generated feature parses cleanly with behave-model.
    try:
        parse_feature(rendered, filename=str(target))
    except ParseError as exc:
        raise AddError(f"Generated feature failed to parse: {exc}") from exc

    try:
        safe_write_text(target, rendered)
    except OSError as exc:
        raise AddError(f"Could not write feature file {target}: {exc}") from exc
    return target

behave_gen.commands.add.AddFeatureOptions dataclass

Options for add feature.

Source code in behave_gen/commands/add.py
@dataclass(frozen=True, slots=True)
class AddFeatureOptions:
    """Options for ``add feature``."""

    name: str
    tags: str | None = None
    template: str = "default"

Add steps

behave_gen.commands.steps.add_steps

add_steps(project_root: str | Path, options: AddStepsOptions, *, steps_dir: str | Path = 'features/steps', output_file: str | Path | None = None) -> Path

Copy a step library into project_root's steps directory.

Parameters:

Name Type Description Default
project_root str | Path

Root of the Behave project.

required
options AddStepsOptions

Add-steps options.

required
steps_dir str | Path

Steps directory relative to project_root.

'features/steps'
output_file str | Path | None

Optional explicit target path. When omitted the standard library filename inside steps_dir is used.

None

Returns:

Type Description
Path

The path to the written step-definition file.

Raises:

Type Description
AddStepsError

If the project is missing, the library is unknown, or the file already exists.

Source code in behave_gen/commands/steps.py
def add_steps(
    project_root: str | Path,
    options: AddStepsOptions,
    *,
    steps_dir: str | Path = "features/steps",
    output_file: str | Path | None = None,
) -> Path:
    """Copy a step library into ``project_root``'s steps directory.

    Args:
        project_root: Root of the Behave project.
        options: Add-steps options.
        steps_dir: Steps directory relative to ``project_root``.
        output_file: Optional explicit target path. When omitted the standard
            library filename inside ``steps_dir`` is used.

    Returns:
        The path to the written step-definition file.

    Raises:
        AddStepsError: If the project is missing, the library is unknown, or
            the file already exists.

    """
    if options.lib not in _BUILTIN_LIBRARIES:
        available = ", ".join(_available_libraries())
        raise AddStepsError(f"Unknown step library {options.lib!r}. Available: {available}.")

    root = Path(project_root).resolve()
    if not root.is_dir():
        raise AddStepsError(f"Project root not found: {root}")

    steps = (root / steps_dir).resolve()
    if not steps.is_relative_to(root):
        raise AddStepsError(f"Steps directory {steps} escapes project root {root}.")
    try:
        steps.mkdir(parents=True, exist_ok=True)
    except OSError as exc:
        raise AddStepsError(f"Could not create steps directory {steps}: {exc}") from exc

    template_name, output_name = _BUILTIN_LIBRARIES[options.lib]
    if output_file is None:
        target = steps / output_name
    else:
        target = Path(output_file)
        if not target.is_absolute():
            target = steps / output_file
        target = target.resolve()
    if not target.is_relative_to(steps):
        raise AddStepsError(f"Step file {target} must be inside steps directory {steps}.")
    if target.exists() or target.is_symlink():
        raise AddStepsError(
            f"Step file already exists: {target}. Remove it or choose another library."
        )

    raw = _load_step_template(template_name)
    project_name = root.name.replace("\\", "\\\\").replace('"', '\\"')
    try:
        rendered = string.Template(raw).substitute(project_name=project_name)
    except KeyError as exc:
        key = exc.args[0] if exc.args else "<unknown>"
        raise AddStepsError(f"Missing template variable ${key}.") from exc

    try:
        safe_write_text(target, rendered)
    except OSError as exc:
        raise AddStepsError(f"Could not write step file {target}: {exc}") from exc
    return target

behave_gen.commands.steps.AddStepsOptions dataclass

Options for add steps.

Source code in behave_gen/commands/steps.py
@dataclass(frozen=True, slots=True)
class AddStepsOptions:
    """Options for ``add steps``."""

    lib: str

Add environment

behave_gen.commands.environment.add_environment

add_environment(project_root: str | Path, options: AddEnvironmentOptions, *, environment_file: str | Path = 'environment.py') -> Path

Rewrite environment.py with the requested kit/data wiring.

Parameters:

Name Type Description Default
project_root str | Path

Root of the Behave project.

required
options AddEnvironmentOptions

Add-environment options.

required
environment_file str | Path

Path to the environment file, relative to the project root or absolute.

'environment.py'

Returns:

Type Description
Path

The path to the written environment.py.

Raises:

Type Description
EnvironmentError

If the project root is missing or the file cannot be written.

Source code in behave_gen/commands/environment.py
def add_environment(
    project_root: str | Path,
    options: AddEnvironmentOptions,
    *,
    environment_file: str | Path = "environment.py",
) -> Path:
    """Rewrite ``environment.py`` with the requested kit/data wiring.

    Args:
        project_root: Root of the Behave project.
        options: Add-environment options.
        environment_file: Path to the environment file, relative to the project
            root or absolute.

    Returns:
        The path to the written ``environment.py``.

    Raises:
        EnvironmentError: If the project root is missing or the file cannot be
            written.

    """
    root = Path(project_root).resolve()
    if not root.is_dir():
        raise EnvironmentError(f"Project root not found: {root}")

    variant = environment_variant(options.kit, options.data)
    raw = _load_environment_template(variant)

    project_name = _project_name(root)
    try:
        rendered = string.Template(raw).substitute(
            project_name=project_name.replace("\\", "\\\\").replace('"', '\\"'),
        )
    except KeyError as exc:
        key = exc.args[0] if exc.args else "<unknown>"
        raise EnvironmentError(f"Missing template variable ${key}.") from exc

    target = Path(environment_file)
    if not target.is_absolute():
        target = root / environment_file
    target = target.resolve()
    if not target.is_relative_to(root):
        raise EnvironmentError(f"Environment file {target} must be inside project root {root}.")
    if target.is_dir() and not target.is_symlink():
        raise EnvironmentError(f"Cannot overwrite directory: {target}")

    try:
        target.parent.mkdir(parents=True, exist_ok=True)
    except OSError as exc:
        raise EnvironmentError(f"Could not create parent directory {target.parent}: {exc}") from exc

    try:
        safe_write_text(target, rendered)
    except OSError as exc:
        raise EnvironmentError(f"Could not write environment file {target}: {exc}") from exc
    return target

behave_gen.commands.environment.AddEnvironmentOptions dataclass

Options for add environment.

Source code in behave_gen/commands/environment.py
@dataclass(frozen=True, slots=True)
class AddEnvironmentOptions:
    """Options for ``add environment``."""

    kit: bool = False
    data: bool = False

Configuration

behave_gen.config.BehaveGenConfig dataclass

Immutable behave-gen configuration.

Defaults are deterministic and independent of the host environment so that identical inputs always produce identical projects.

Source code in behave_gen/config.py
@dataclass(frozen=True, slots=True)
class BehaveGenConfig:
    """Immutable behave-gen configuration.

    Defaults are deterministic and independent of the host environment so that
    identical inputs always produce identical projects.
    """

    features_dir: str = "features"
    steps_dir: str = "features/steps"
    environment_file: str = "environment.py"
    templates_dir: str = "templates"
    template_engine: str = "string"
    default_tags: tuple[str, ...] = field(default_factory=tuple)

    def __post_init__(self) -> None:
        """Validate the template engine and normalise default tags."""
        if self.template_engine not in _VALID_TEMPLATE_ENGINES:
            valid = ", ".join(sorted(_VALID_TEMPLATE_ENGINES))
            raise ValueError(
                f"Invalid template_engine {self.template_engine!r}. Valid values: {valid}."
            )
        object.__setattr__(
            self,
            "default_tags",
            tuple(_normalize_tags(self.default_tags)),
        )

    @classmethod
    def default(cls) -> BehaveGenConfig:
        """Return the default configuration."""
        return cls()

    def with_overrides(self, **overrides: Any) -> BehaveGenConfig:  # noqa: ANN401
        """Return a new config with the given overrides applied."""
        known = {f.name for f in fields(self)}
        unknown = set(overrides) - known
        if unknown:
            raise ValueError(f"Unknown config keys: {sorted(unknown)}")
        return type(self)(**{**self.as_dict(), **overrides})

    def as_dict(self) -> dict[str, Any]:
        """Return the config as a plain dictionary."""
        return {f.name: getattr(self, f.name) for f in fields(self)}

__post_init__

__post_init__() -> None

Validate the template engine and normalise default tags.

Source code in behave_gen/config.py
def __post_init__(self) -> None:
    """Validate the template engine and normalise default tags."""
    if self.template_engine not in _VALID_TEMPLATE_ENGINES:
        valid = ", ".join(sorted(_VALID_TEMPLATE_ENGINES))
        raise ValueError(
            f"Invalid template_engine {self.template_engine!r}. Valid values: {valid}."
        )
    object.__setattr__(
        self,
        "default_tags",
        tuple(_normalize_tags(self.default_tags)),
    )

as_dict

as_dict() -> dict[str, Any]

Return the config as a plain dictionary.

Source code in behave_gen/config.py
def as_dict(self) -> dict[str, Any]:
    """Return the config as a plain dictionary."""
    return {f.name: getattr(self, f.name) for f in fields(self)}

default classmethod

default() -> BehaveGenConfig

Return the default configuration.

Source code in behave_gen/config.py
@classmethod
def default(cls) -> BehaveGenConfig:
    """Return the default configuration."""
    return cls()

with_overrides

with_overrides(**overrides: Any) -> BehaveGenConfig

Return a new config with the given overrides applied.

Source code in behave_gen/config.py
def with_overrides(self, **overrides: Any) -> BehaveGenConfig:  # noqa: ANN401
    """Return a new config with the given overrides applied."""
    known = {f.name for f in fields(self)}
    unknown = set(overrides) - known
    if unknown:
        raise ValueError(f"Unknown config keys: {sorted(unknown)}")
    return type(self)(**{**self.as_dict(), **overrides})

behave_gen.config.load_config

load_config(root: str | Path) -> BehaveGenConfig

Load behave-gen configuration from root/pyproject.toml.

Falls back to :meth:BehaveGenConfig.default when the file or the [tool.behave-gen] table is missing.

Parameters:

Name Type Description Default
root str | Path

Project root containing pyproject.toml.

required

Returns:

Type Description
BehaveGenConfig

A frozen :class:BehaveGenConfig.

Raises:

Type Description
FileNotFoundError

If root is not a directory.

ValueError

If the config contains unknown keys or invalid values.

Source code in behave_gen/config.py
def load_config(root: str | Path) -> BehaveGenConfig:
    """Load behave-gen configuration from ``root/pyproject.toml``.

    Falls back to :meth:`BehaveGenConfig.default` when the file or the
    ``[tool.behave-gen]`` table is missing.

    Args:
        root: Project root containing ``pyproject.toml``.

    Returns:
        A frozen :class:`BehaveGenConfig`.

    Raises:
        FileNotFoundError: If ``root`` is not a directory.
        ValueError: If the config contains unknown keys or invalid values.

    """
    root_path = Path(root)
    if not root_path.is_dir():
        raise FileNotFoundError(f"Project root not found: {root_path}")

    pyproject = root_path / "pyproject.toml"
    if not pyproject.is_file():
        return BehaveGenConfig.default()

    return load_config_at(pyproject)

Check

behave_gen.commands.check.run_check

run_check(project_root: str | Path | None = None, *, fmt: str = 'text', config: BehaveGenConfig | None = None) -> int

CLI entry point for behave-gen check.

Returns the exit code: 0 when behave-doctor is missing (graceful) or when the report has no errors; otherwise the doctor report's exit code.

Source code in behave_gen/commands/check.py
def run_check(
    project_root: str | Path | None = None,
    *,
    fmt: str = "text",
    config: BehaveGenConfig | None = None,
) -> int:
    """CLI entry point for ``behave-gen check``.

    Returns the exit code: ``0`` when behave-doctor is missing (graceful) or
    when the report has no errors; otherwise the doctor report's exit code.
    """
    root = resolve_project_root(project_root)
    try:
        project = Project.from_root(root, config=config)
    except ProjectError as exc:
        print(f"check: {exc}", file=sys.stderr)
        return 1

    fmt_normalized = fmt.lower()
    if fmt_normalized not in {"text", "json"}:
        print(f"check: Invalid format {fmt!r}. Use 'text' or 'json'.", file=sys.stderr)
        return 1

    status = check_extra("doctor")
    if not status.available:
        report = CheckReport(
            project=str(project.root),
            available=False,
            install_hint=status.install_hint,
        )
        _emit(report, fmt_normalized)
        return 0

    import behave_doctor  # noqa: PLC0415 - optional extra imported lazily.

    try:
        raw = behave_doctor.scan_project(project.root)
    except Exception as exc:  # noqa: BLE001 - surface doctor failures cleanly.
        print(f"check: behave-doctor failed: {exc}", file=sys.stderr)
        return 1

    report = _build_report_from_doctor(project.root, raw)
    _emit(report, fmt_normalized)
    return report.exit_code

Stats

behave_gen.commands.stats.run_stats

run_stats(project_root: str | Path | None = None, *, fmt: str = 'text', config: BehaveGenConfig | None = None) -> int

CLI entry point for behave-gen stats.

Source code in behave_gen/commands/stats.py
def run_stats(
    project_root: str | Path | None = None,
    *,
    fmt: str = "text",
    config: BehaveGenConfig | None = None,
) -> int:
    """CLI entry point for ``behave-gen stats``."""
    root = resolve_project_root(project_root)
    try:
        project = Project.from_root(root, config=config)
    except ProjectError as exc:
        print(f"stats: {exc}", file=sys.stderr)
        return 1

    fmt_normalized = fmt.lower()
    if fmt_normalized not in {"text", "json"}:
        print(f"stats: Invalid format {fmt!r}. Use 'text' or 'json'.", file=sys.stderr)
        return 1

    report = _collect_stats(project)

    if fmt_normalized == "json":
        print(json.dumps(report.to_dict(), indent=2, sort_keys=True))
        return 0

    print(f"Project: {report.project}")
    print(f"  Features:         {report.features}")
    print(f"  Scenarios:        {report.scenarios}")
    print(f"  Scenario outlines: {report.scenarios_outline}")
    print(f"  Steps:            {report.steps}")
    print(f"  Tags:             {report.tags}")
    print(f"  Files:            {len(report.files)}")
    for warning in report.warnings:
        print(f"  warning: {warning}", file=sys.stderr)
    return 0

Preview

behave_gen.commands.preview.run_preview

run_preview(feature_path: str, project_root: str | Path | None = None, *, config: BehaveGenConfig | None = None) -> int

CLI entry point for behave-gen preview.

Source code in behave_gen/commands/preview.py
def run_preview(
    feature_path: str,
    project_root: str | Path | None = None,
    *,
    config: BehaveGenConfig | None = None,
) -> int:
    """CLI entry point for ``behave-gen preview``."""
    root = resolve_project_root(project_root)
    try:
        project = Project.from_root(root, config=config)
    except ProjectError as exc:
        print(f"preview: {exc}", file=sys.stderr)
        return 1

    path, error = _resolve_preview_path(feature_path, project.root)
    if error is not None:
        print(f"preview: {error}", file=sys.stderr)
        return 1
    if path is None:
        return 1

    try:
        text = path.read_text(encoding="utf-8")
    except (OSError, UnicodeDecodeError) as exc:
        kind = "read" if isinstance(exc, OSError) else "decode as UTF-8"
        print(f"preview: Could not {kind} {path}: {exc}", file=sys.stderr)
        return 1
    try:
        feature = parse_feature(text, filename=str(path))
    except ParseError as exc:
        print(f"preview: Parse error: {exc}", file=sys.stderr)
        return 1

    print(render_feature(feature))
    return 0

Update

behave_gen.commands.update.run_update

run_update(options: UpdateOptions, project_root: str | Path | None = None, *, config: BehaveGenConfig | None = None) -> int

CLI entry point for behave-gen update.

Source code in behave_gen/commands/update.py
def run_update(
    options: UpdateOptions,
    project_root: str | Path | None = None,
    *,
    config: BehaveGenConfig | None = None,
) -> int:
    """CLI entry point for ``behave-gen update``."""
    root = resolve_project_root(project_root)
    try:
        project = Project.from_root(root, config=config)
    except ProjectError as exc:
        print(f"update: {exc}", file=sys.stderr)
        return 1

    all_updated: list[str] = []
    all_skipped: list[str] = []
    all_warnings: list[str] = []

    env_updated, env_warnings = _update_environment(project, options)
    all_updated.extend(env_updated)
    all_warnings.extend(env_warnings)

    steps_updated, steps_skipped, steps_warnings = _update_step_libraries(project, options.force)
    all_updated.extend(steps_updated)
    all_skipped.extend(steps_skipped)
    all_warnings.extend(steps_warnings)

    for path in all_updated:
        print(f"Updated {path}")
    for path in all_skipped:
        print(f"Skipped {path}", file=sys.stderr)
    for warning in all_warnings:
        print(f"update: warning: {warning}", file=sys.stderr)

    if not all_updated and not all_skipped:
        print("Nothing to update. No behave-gen generated files found.")
    else:
        print(f"\nUpdated {len(all_updated)} file(s).")
    return 0

behave_gen.commands.update.UpdateOptions dataclass

Options for update.

Source code in behave_gen/commands/update.py
@dataclass(frozen=True, slots=True)
class UpdateOptions:
    """Options for ``update``."""

    force: bool = False
    kit: bool = False
    data: bool = False

Generators

behave_gen.commands.from_openapi.run_from_openapi

run_from_openapi(options: FromOpenApiOptions, project_root: str | Path | None = None, *, config: BehaveGenConfig | None = None) -> int

CLI entry point for behave-gen from-openapi.

Source code in behave_gen/commands/from_openapi.py
def run_from_openapi(
    options: FromOpenApiOptions,
    project_root: str | Path | None = None,
    *,
    config: BehaveGenConfig | None = None,
) -> int:
    """CLI entry point for ``behave-gen from-openapi``."""
    root = resolve_project_root(project_root)
    try:
        project = Project.from_root(root, config=config)
    except ProjectError as exc:
        print(f"from-openapi: {exc}", file=sys.stderr)
        return 1

    spec_path = Path(options.spec)
    if not spec_path.is_absolute():
        spec_path = (project.root / spec_path).resolve()
        if not spec_path.is_relative_to(project.root):
            print(
                f"from-openapi: Spec path must be inside project root: {spec_path}",
                file=sys.stderr,
            )
            return 1

    out_dir = Path(options.out_dir)
    if not out_dir.is_absolute():
        out_dir = project.root / out_dir
    out_dir = out_dir.resolve()
    if not out_dir.is_relative_to(project.root):
        print(
            f"from-openapi: Output directory must be inside project root: {out_dir}",
            file=sys.stderr,
        )
        return 1

    if options.step_lib is not None and options.step_lib != "http":
        print(
            "from-openapi: Only the 'http' step library is supported for generated specs.",
            file=sys.stderr,
        )
        return 1

    generator = OpenApiGenerator()
    try:
        result = generator.generate(
            spec_path,
            out_dir,
            step_lib=options.step_lib,
            tag=options.tag,
            default_tags=project.config.default_tags,
            include_paths=list(options.include_paths) or None,
            include_methods=list(options.include_methods) or None,
            project_name=project.root.name,
        )
    except (OpenApiParseError, OSError) as exc:
        print(f"from-openapi: {exc}", file=sys.stderr)
        return 1

    for feature in result.features:
        print(f"Created feature {feature}")
    for steps_file in result.steps:
        print(f"Created step library {steps_file}")
    for warning in result.warnings:
        print(f"from-openapi: warning: {warning}", file=sys.stderr)
    return 0

behave_gen.commands.from_openapi.FromOpenApiOptions dataclass

Options for from-openapi.

Source code in behave_gen/commands/from_openapi.py
@dataclass(frozen=True, slots=True)
class FromOpenApiOptions:
    """Options for ``from-openapi``."""

    spec: str
    out_dir: str = "gen"
    step_lib: str | None = None
    tag: str | None = None
    include_paths: tuple[str, ...] = ()
    include_methods: tuple[str, ...] = ()

behave_gen.commands.from_postman.run_from_postman

run_from_postman(options: FromPostmanOptions, project_root: str | Path | None = None, *, config: BehaveGenConfig | None = None) -> int

CLI entry point for behave-gen from-postman.

Source code in behave_gen/commands/from_postman.py
def run_from_postman(
    options: FromPostmanOptions,
    project_root: str | Path | None = None,
    *,
    config: BehaveGenConfig | None = None,
) -> int:
    """CLI entry point for ``behave-gen from-postman``."""
    root = resolve_project_root(project_root)
    try:
        project = Project.from_root(root, config=config)
    except ProjectError as exc:
        print(f"from-postman: {exc}", file=sys.stderr)
        return 1

    collection_path = Path(options.collection)
    if not collection_path.is_absolute():
        collection_path = (project.root / collection_path).resolve()
        if not collection_path.is_relative_to(project.root):
            print(
                f"from-postman: Collection path must be inside project root: {collection_path}",
                file=sys.stderr,
            )
            return 1

    out_dir = Path(options.out_dir)
    if not out_dir.is_absolute():
        out_dir = project.root / out_dir
    out_dir = out_dir.resolve()
    if not out_dir.is_relative_to(project.root):
        print(
            f"from-postman: Output directory must be inside project root: {out_dir}",
            file=sys.stderr,
        )
        return 1

    if options.step_lib is not None and options.step_lib != "http":
        print(
            "from-postman: Only the 'http' step library is supported for generated specs.",
            file=sys.stderr,
        )
        return 1

    generator = PostmanGenerator()
    try:
        result = generator.generate(
            collection_path,
            out_dir,
            step_lib=options.step_lib,
            tag=options.tag,
            default_tags=project.config.default_tags,
            project_name=project.root.name,
        )
    except (PostmanParseError, OSError) as exc:
        print(f"from-postman: {exc}", file=sys.stderr)
        return 1

    for feature in result.features:
        print(f"Created feature {feature}")
    for steps_file in result.steps:
        print(f"Created step library {steps_file}")
    for warning in result.warnings:
        print(f"from-postman: warning: {warning}", file=sys.stderr)
    return 0

behave_gen.commands.from_postman.FromPostmanOptions dataclass

Options for from-postman.

Source code in behave_gen/commands/from_postman.py
@dataclass(frozen=True, slots=True)
class FromPostmanOptions:
    """Options for ``from-postman``."""

    collection: str
    out_dir: str = "gen"
    step_lib: str | None = None
    tag: str | None = None

behave_gen.commands.from_swagger.run_from_swagger

run_from_swagger(options: FromSwaggerOptions, project_root: str | Path | None = None, *, config: BehaveGenConfig | None = None) -> int

CLI entry point for behave-gen from-swagger.

Source code in behave_gen/commands/from_swagger.py
def run_from_swagger(
    options: FromSwaggerOptions,
    project_root: str | Path | None = None,
    *,
    config: BehaveGenConfig | None = None,
) -> int:
    """CLI entry point for ``behave-gen from-swagger``."""
    root = resolve_project_root(project_root)
    try:
        project = Project.from_root(root, config=config)
    except ProjectError as exc:
        print(f"from-swagger: {exc}", file=sys.stderr)
        return 1

    spec_path = Path(options.spec)
    if not spec_path.is_absolute():
        spec_path = (project.root / spec_path).resolve()
        if not spec_path.is_relative_to(project.root):
            print(
                f"from-swagger: Spec path must be inside project root: {spec_path}",
                file=sys.stderr,
            )
            return 1

    out_dir = Path(options.out_dir)
    if not out_dir.is_absolute():
        out_dir = project.root / out_dir
    out_dir = out_dir.resolve()
    if not out_dir.is_relative_to(project.root):
        print(
            f"from-swagger: Output directory must be inside project root: {out_dir}",
            file=sys.stderr,
        )
        return 1

    if options.step_lib is not None and options.step_lib != "http":
        print(
            "from-swagger: Only the 'http' step library is supported for generated specs.",
            file=sys.stderr,
        )
        return 1

    generator = SwaggerGenerator()
    try:
        result = generator.generate(
            spec_path,
            out_dir,
            step_lib=options.step_lib,
            tag=options.tag,
            default_tags=project.config.default_tags,
            include_paths=list(options.include_paths) or None,
            include_methods=list(options.include_methods) or None,
            project_name=project.root.name,
        )
    except (SwaggerParseError, OSError) as exc:
        print(f"from-swagger: {exc}", file=sys.stderr)
        return 1

    for feature in result.features:
        print(f"Created feature {feature}")
    for steps_file in result.steps:
        print(f"Created step library {steps_file}")
    for warning in result.warnings:
        print(f"from-swagger: warning: {warning}", file=sys.stderr)
    return 0

behave_gen.commands.from_swagger.FromSwaggerOptions dataclass

Options for from-swagger.

Source code in behave_gen/commands/from_swagger.py
@dataclass(frozen=True, slots=True)
class FromSwaggerOptions:
    """Options for ``from-swagger``."""

    spec: str
    out_dir: str = "gen"
    step_lib: str | None = None
    tag: str | None = None
    include_paths: tuple[str, ...] = ()
    include_methods: tuple[str, ...] = ()

Migration

behave_gen.commands.migrate.run_migrate

run_migrate(options: MigrateOptions, project_root: str | Path | None = None, *, config: BehaveGenConfig | None = None) -> int

CLI entry point for behave-gen migrate.

Source code in behave_gen/commands/migrate.py
def run_migrate(
    options: MigrateOptions,
    project_root: str | Path | None = None,
    *,
    config: BehaveGenConfig | None = None,
) -> int:
    """CLI entry point for ``behave-gen migrate``."""
    root = resolve_project_root(project_root)
    try:
        project = Project.from_root(root, config=config)
    except ProjectError as exc:
        print(f"migrate: {exc}", file=sys.stderr)
        return 1

    source = Path(options.source)
    if not source.is_absolute():
        source = (project.root / source).resolve()
        if not source.is_relative_to(project.root):
            print(
                f"migrate: Source path must be inside project root: {source}",
                file=sys.stderr,
            )
            return 1

    out_dir = Path(options.out_dir)
    if not out_dir.is_absolute():
        out_dir = project.root / out_dir
    out_dir = out_dir.resolve()
    if not out_dir.is_relative_to(project.root):
        print(
            f"migrate: Output directory must be inside project root: {out_dir}",
            file=sys.stderr,
        )
        return 1

    try:
        report = migrate_cucumber(source, out_dir)
    except MigrationError as exc:
        print(f"migrate: {exc}", file=sys.stderr)
        return 1
    except OSError as exc:
        print(f"migrate: {exc}", file=sys.stderr)
        return 1

    for feature in report.features:
        print(f"Migrated feature {feature}")
    for skipped in report.skipped:
        print(f"migrate: skipped {skipped}", file=sys.stderr)
    for warning in report.warnings:
        print(f"migrate: warning: {warning}", file=sys.stderr)
    print(f"\nMigrated {len(report.features)} feature file(s).")
    return 0

behave_gen.commands.migrate.MigrateOptions dataclass

Options for migrate.

Source code in behave_gen/commands/migrate.py
@dataclass(frozen=True, slots=True)
class MigrateOptions:
    """Options for ``migrate``."""

    source: str
    out_dir: str = "migrated"