"""Module loaders for dynamic test discovery."""
import importlib.util
import sys
from pathlib import Path
from typing import Callable, List, Optional, cast
[docs]
class ModuleLoader:
"""Handles discovery and dynamic loading of Python modules from file paths."""
[docs]
@staticmethod
def load_runnable_from_file(file_path: Path, function_name: str = "run") -> Optional[Callable]:
"""Load a specific function from a python file."""
if not file_path.exists() or file_path.suffix != ".py":
return None
try:
# Create a unique module name based on path to avoid collisions
module_name = f"dynamic_mod_{file_path.stem}_{hash(str(file_path)) % 10000}"
spec = importlib.util.spec_from_file_location(module_name, str(file_path))
if spec and spec.loader:
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)
func = getattr(module, function_name, None)
if callable(func):
return cast(Callable, func)
except Exception as e:
print(f"Error loading {file_path}: {e}")
return None
[docs]
def discover_runnables(self, root_path: Path, pattern: str = "*.py") -> List[Callable]:
"""Discovers all runnable modules in a directory matching a pattern."""
runnables: List[Callable] = []
if not root_path.exists() or not root_path.is_dir():
return runnables
# Sort by filename to ensure predictable order
paths = sorted(list(root_path.glob(pattern)), key=lambda p: p.name)
for file_path in paths:
if file_path.name == "__init__.py":
continue
runnable = self.load_runnable_from_file(file_path)
if runnable:
runnables.append(runnable)
return runnables