diff --git a/fromconfig/launcher/base.py b/fromconfig/launcher/base.py index e3ecbd7..3710ba3 100644 --- a/fromconfig/launcher/base.py +++ b/fromconfig/launcher/base.py @@ -1,10 +1,14 @@ """Base class for launchers.""" from abc import ABC +import sys from typing import Any, Dict, Type import inspect import logging -import pkg_resources +try: + from importlib.metadata import entry_points +except ImportError: + from importlib_metadata import entry_points from fromconfig.core.base import fromconfig, FromConfig, Keys from fromconfig.utils.libimport import from_import_string @@ -136,8 +140,13 @@ def _load(): _CLASSES["parser"] = ParserLauncher _CLASSES["dry"] = DryLauncher + if sys.version_info >= (3, 10): + eps = entry_points(group=f"fromconfig{__major__}") + else: + eps = entry_points().get(f"fromconfig{__major__}", []) + # Load external classes, use entry point's name for reference - for entry_point in pkg_resources.iter_entry_points(f"fromconfig{__major__}"): + for entry_point in eps: module = entry_point.load() for _, cls in inspect.getmembers(module, lambda m: inspect.isclass(m) and issubclass(m, Launcher)): name = f"{entry_point.name}.{cls.NAME}" if hasattr(cls, "NAME") else entry_point.name diff --git a/requirements-test.txt b/requirements-test.txt index 739cacd..011d575 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,6 +1,6 @@ -black==22.6.0 -mypy==0.790 -pylint==2.6.0 -pytest-cov==2.10.1 -pytest-xdist==2.1.0 -pytest==6.1.2 +black==25.11.0 +mypy==1.19.0 +pylint==4.0.4 +pytest-cov==7.0.0 +pytest-xdist==3.8.0 +pytest==9.0.1 diff --git a/requirements.txt b/requirements.txt index ee2f3c8..4d9b6a2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -fire==0.4.0 -jsonnet==0.17.0 -PyYAML==5.4.1 -omegaconf==2.1.1 +fire==0.7.1 +jsonnet==0.21.0 +PyYAML==6.0.3 +omegaconf==2.3.0 diff --git a/tests/unit/cli/test_cli_main.py b/tests/unit/cli/test_cli_main.py index e8b47ad..451e774 100644 --- a/tests/unit/cli/test_cli_main.py +++ b/tests/unit/cli/test_cli_main.py @@ -12,6 +12,9 @@ def capture(command): """Utility to execute and capture the result of a command.""" proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = proc.communicate() + # Normalize line endings for cross-platform compatibility (Windows uses \r\n) + out = out.replace(b'\r\n', b'\n') + err = err.replace(b'\r\n', b'\n') return out, err, proc.returncode diff --git a/tests/unit/launcher/test_launcher_base.py b/tests/unit/launcher/test_launcher_base.py index ba378bd..d04f386 100644 --- a/tests/unit/launcher/test_launcher_base.py +++ b/tests/unit/launcher/test_launcher_base.py @@ -2,7 +2,7 @@ """Test launcher.base.""" import pytest -import pkg_resources +import sys from typing import Any import fromconfig @@ -46,7 +46,7 @@ def test_launcher_classes(name, expected): def test_launcher_classes_extension(monkeypatch): """Test launcher classes extension.""" - + fromconfig.launcher.base._CLASSES.clear() # Clear internal and external launchers class DummyModule: @@ -54,7 +54,7 @@ class DummyLauncher(fromconfig.launcher.Launcher): def __call__(self, config: Any, command: str = ""): ... - class EntryPoint: + class MockEntryPoint: def __init__(self, name, module): self.name = name self.module = module @@ -62,9 +62,27 @@ def __init__(self, name, module): def load(self): return self.module - # Test discovery - monkeypatch.setattr(pkg_resources, "iter_entry_points", lambda *_: [EntryPoint("dummy", DummyModule)]) + mock_called = {"called": False} + + def mock_entry_points(group=None): + mock_called["called"] = True + # For Python 3.10+ + if sys.version_info >= (3, 10): + # Return list directly when called with group parameter + return [MockEntryPoint("dummy", DummyModule)] + else: + # For Python 3.8-3.9, return dict-like object + class MockEntryPoints: + def get(self, group_name, default=None): + return [MockEntryPoint("dummy", DummyModule)] + return MockEntryPoints() + + monkeypatch.setattr("fromconfig.launcher.base.entry_points", mock_entry_points) + fromconfig.launcher.base._load() + + assert mock_called["called"], "Mock entry_points was never called" + fromconfig.utils.testing.assert_launcher_is_discovered("dummy", DummyModule.DummyLauncher) assert fromconfig.launcher.base._classes()["dummy"] == DummyModule.DummyLauncher @@ -110,4 +128,4 @@ def test_launcher_fromconfig(config, expected): assert_equal_launcher(fromconfig.launcher.Launcher.fromconfig(config), expected) else: with pytest.raises(expected): - fromconfig.launcher.Launcher.fromconfig(config) + fromconfig.launcher.Launcher.fromconfig(config) \ No newline at end of file