Skip to content
Merged
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
51 changes: 46 additions & 5 deletions src/wrappers/OsipiBase.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,19 +145,60 @@ def osipi_initiate_algorithm(self, algorithm, **kwargs):

Args:
algorithm (string): The name of the algorithm, should be the same as the file in the src/standardized folder without the .py extension.

Raises:
ValueError: If the algorithm name does not correspond to any .py file
in ``src/standardized/``.
"""

# Import the algorithm
root_path = pathlib.Path(__file__).resolve().parents[2]
if str(root_path) not in sys.path:
print("Root folder not in PYTHONPATH")
return False

raise RuntimeError(
"Root folder not found in PYTHONPATH. "
"Please ensure the project root is in sys.path."
)

# ------------------------------------------------------------------
# Validate algorithm name against available modules in src/standardized/
# ------------------------------------------------------------------
standardized_dir = root_path / "src" / "standardized"
available_algorithms = sorted(
p.stem
for p in standardized_dir.glob("*.py")
if p.stem != "__init__"
)

if algorithm not in available_algorithms:
raise ValueError(
f"Algorithm '{algorithm}' not found. "
f"Available algorithms are:\n "
+ "\n ".join(available_algorithms)
)

import_base_path = "src.standardized"
import_path = import_base_path + "." + algorithm
#Algorithm = getattr(importlib.import_module(import_path), algorithm)

# Secondary safety net: catch import / attribute errors that could
# occur if the file exists but is broken or missing the class.
try:
module = importlib.import_module(import_path)
except ImportError as exc:
raise ImportError(
f"Failed to import module for algorithm '{algorithm}': {exc}"
) from exc

try:
algorithm_class = getattr(module, algorithm)
except AttributeError as exc:
raise AttributeError(
f"Module '{import_path}' was imported but does not contain "
f"a class named '{algorithm}'. "
f"Available names: {[n for n in dir(module) if not n.startswith('_')]}"
) from exc

# Change the class from OsipiBase to the specified algorithm
self.__class__ = getattr(importlib.import_module(import_path), algorithm)
self.__class__ = algorithm_class
self.__init__(**kwargs)

def initialize(**kwargs):
Expand Down
25 changes: 25 additions & 0 deletions tests/IVIMmodels/unit_tests/test_ivim_fit.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,3 +300,28 @@ def to_list_if_needed(value):
if errors:
all_errors = "\n".join(errors)
raise AssertionError(f"Some tests failed:\n{all_errors}")


def test_invalid_algorithm_name():
"""Regression test for bug_7: invalid algorithm names must raise a clear
ValueError instead of a confusing ModuleNotFoundError / AttributeError.
"""
# A clearly wrong name should raise ValueError
with pytest.raises(ValueError, match="not found"):
OsipiBase(algorithm="IAR_LU_biepx") # typo: 'biepx' instead of 'biexp'

# The error message should list at least one known valid algorithm
try:
OsipiBase(algorithm="totally_fake_algorithm")
except ValueError as exc:
error_msg = str(exc)
assert "Available algorithms are:" in error_msg
# Check a few known algorithms appear in the suggestion list
assert "IAR_LU_biexp" in error_msg
assert "PV_MUMC_biexp" in error_msg
else:
pytest.fail("ValueError was not raised for a completely invalid algorithm name")

# A valid algorithm should still work without errors
fit = OsipiBase(algorithm="IAR_LU_biexp")
assert fit is not None
Loading