diff --git a/features/steps/security_pyyaml_dependency_steps.py b/features/steps/security_pyyaml_dependency_steps.py index ef9e98579..6518557ff 100644 --- a/features/steps/security_pyyaml_dependency_steps.py +++ b/features/steps/security_pyyaml_dependency_steps.py @@ -8,6 +8,7 @@ from pathlib import Path from typing import Any from behave import given, then, when +from packaging.version import parse as parse_version from behave.runner import Context @@ -69,8 +70,8 @@ def step_read_pyyaml_dependency(context: Context) -> None: def step_pyyaml_minimum_version(context: Context, expected: str) -> None: """Assert that the PyYAML minimum version is >= expected version.""" min_version = context.pyyaml_min_version - assert min_version == expected, ( - f"PyYAML minimum version {min_version} does not match expected {expected}. " + assert parse_version(min_version) >= parse_version(expected), ( + f"PyYAML minimum version {min_version} is less than required {expected}. " f"Dependency spec: {context.pyyaml_version_spec}" ) @@ -125,8 +126,25 @@ def _load_toml(content: bytes) -> dict[str, Any]: import tomllib return tomllib.loads(content.decode("utf-8")) - except ImportError: + except ImportError: # pragma: no cover — Python <3.11 # Fallback for older Python versions without tomllib import tomlkit - return tomlkit.parse(content.decode("utf-8")).unwrap() # type: ignore[no-any-return] + parsed_doc = tomlkit.parse(content.decode("utf-8")) + return _toml_to_python(parsed_doc) + + +def _toml_to_python(data: object) -> dict[str, Any]: + """Convert a TOML document from tomlkit (or tomllib) to plain Python dicts.""" + if isinstance(data, dict): + def _flatten(d: dict[Any, Any]) -> dict[str, Any]: + result: dict[str, Any] = {} + for key, value in d.items(): + if isinstance(value, dict): + result[key] = _flatten(value) + else: + result[key] = value + return result + return _flatten(data) + raise TypeError(f"Expected dict-like TOML table, got {type(data).__name__}") +