Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@ class InstalledPackagesFeatureGroup(FeatureGroup):
- Uses `subprocess.run()` to execute `pip freeze`
- Captures stdout as text
- Returns packages as a single string in a list (DataFrame-compatible)
- On error, returns error message dictionary
- On error, returns the error message in that same column, so the output shape
does not change between the success and failure paths

## Security Considerations

Expand All @@ -108,7 +109,7 @@ def calculate_feature(cls, data: Any, features: FeatureSet) -> Any:
return {cls.get_class_name(): [packages]}
except subprocess.CalledProcessError as e:
error_message = f"Command '{e.cmd}' failed with return code {e.returncode}. Error output: {e.stderr}"
return {"error": error_message}
return {cls.get_class_name(): [error_message]}

@classmethod
def compute_framework_rule(cls) -> set[type[ComputeFramework]]:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import subprocess
from unittest.mock import patch

from mloda.user import Feature
from mloda.provider import FeatureSet
from mloda_plugins.feature_group.experimental.environment.installed_packages_feature_group import (
Expand All @@ -19,3 +22,24 @@ def test_installed_packages_feature_group_mlodaAPI() -> None:
result = mloda.run_all(features, compute_frameworks={PandasDataFrame})
assert len(result) == 1
assert InstalledPackagesFeatureGroup.get_class_name() in result[0]


def test_installed_packages_feature_group_error_path_keeps_the_output_shape() -> None:
"""A pip freeze failure must land in the documented column, not a separate "error" key.

The success path returns {ClassName: [...]}, and the class docstring's Output
Format section promises that single column. A different key on failure gives a
caller a KeyError instead of a readable message, exactly when it is debugging.
"""
feature_set = FeatureSet()
failure = subprocess.CalledProcessError(returncode=1, cmd=["pip", "freeze"], stderr="boom")

with patch("subprocess.run", side_effect=failure):
result = InstalledPackagesFeatureGroup.calculate_feature(None, feature_set)

column = InstalledPackagesFeatureGroup.get_class_name()
assert set(result) == {column}, "the failure path must not introduce another key"
assert isinstance(result[column], list)
assert len(result[column]) == 1
assert "boom" in result[column][0]
assert "return code 1" in result[column][0]
Loading