diff --git a/ctapipe/core/provenance.py b/ctapipe/core/provenance.py index 93b41f1879b..14f6e1edf0e 100644 --- a/ctapipe/core/provenance.py +++ b/ctapipe/core/provenance.py @@ -47,7 +47,7 @@ def get_module_version(name): try: module = import_module(name) return module.__version__ - except AttributeError: + except (AttributeError, ModuleNotFoundError): try: return version(name) except Exception: diff --git a/ctapipe/tools/info.py b/ctapipe/tools/info.py index 8ded56de360..c8d89706a2a 100644 --- a/ctapipe/tools/info.py +++ b/ctapipe/tools/info.py @@ -2,7 +2,9 @@ """ print information about ctapipe and its command-line tools. """ import logging import os +import re import sys +from importlib.metadata import metadata, requires from importlib.resources import files from ..core import Provenance, get_module_version @@ -12,27 +14,6 @@ __all__ = ["info"] -# TODO: this list should be global (or generated at install time) -_dependencies = sorted( - [ - "astropy", - "matplotlib", - "numpy", - "traitlets", - "sklearn", - "scipy", - "numba", - "pytest", - "iminuit", - "tables", - "eventio", - ] -) - -_optional_dependencies = sorted( - ["ctapipe_resources", "pytest", "graphviz", "matplotlib"] -) - def main(args=None): parser = get_parser(info) @@ -78,6 +59,12 @@ def main(args=None): info(**vars(args)) +def pretty_print_requires(package): + pack_name = re.split(";|=|>|<|@|~| ", package)[0] + entry = f"{pack_name} -- {get_module_version(pack_name)}" + return entry + + def info( version=False, tools=False, @@ -160,17 +147,32 @@ def _info_tools(): def _info_dependencies(): """Print info about dependencies.""" - print("\n*** ctapipe core dependencies ***\n") - for name in _dependencies: - version = get_module_version(name) - print(f"{name:>20s} -- {version}") + meta = metadata("ctapipe") + extras = [v for k, v in meta.items() if k == "Provides-Extra"] + + all_dependencies = set(requires("ctapipe")) - print("\n*** ctapipe optional dependencies ***\n") + optional_dependencies = {extra: [] for extra in extras} - for name in _optional_dependencies: - version = get_module_version(name) - print(f"{name:>20s} -- {version}") + required_dependencies = [] + for package in all_dependencies: + if "extra" in package: + for extra in extras: + if extra in package: + optional_dependencies[extra].append(pretty_print_requires(package)) + else: + required_dependencies.append(pretty_print_requires(package)) + + print("\n*** ctapipe core dependencies ***\n") + + for package in required_dependencies: + print(package) + + for extra in optional_dependencies: + print(f"\n*** ctapipe optional dependencies [{extra}] ***\n") + for package in optional_dependencies[extra]: + print(package) def _info_resources(): @@ -215,7 +217,6 @@ def _info_system(): system_prov = prov.current_activity.provenance["system"] for section in ["platform", "python"]: - print("\n====== ", section, " ======== \n") sysinfo = system_prov[section] diff --git a/ctapipe/tools/tests/test_info.py b/ctapipe/tools/tests/test_info.py new file mode 100644 index 00000000000..06e5d8ae78a --- /dev/null +++ b/ctapipe/tools/tests/test_info.py @@ -0,0 +1,78 @@ +"""Test ctapipe-info functionality.""" + + +def test_info_version(script_runner): + + result = script_runner.run( + "ctapipe-info", + "--version", + ) + + assert result.success + assert result.stderr == "" + + +def test_info_tools(script_runner): + + result = script_runner.run( + "ctapipe-info", + "--tools", + ) + + assert result.success + assert result.stderr == "" + + +def test_info_dependencies(script_runner): + + result = script_runner.run( + "ctapipe-info", + "--dependencies", + ) + + assert result.success + assert result.stderr == "" + + +def test_info_system(script_runner): + + result = script_runner.run( + "ctapipe-info", + "--system", + ) + + assert result.success + assert result.stderr == "" + + +def test_info_plugins(script_runner): + + result = script_runner.run( + "ctapipe-info", + "--plugins", + ) + + assert result.success + assert result.stderr == "" + + +def test_info_eventsources(script_runner): + + result = script_runner.run( + "ctapipe-info", + "--event-sources", + ) + + assert result.success + assert result.stderr == "" + + +def test_info_all(script_runner): + + result = script_runner.run( + "ctapipe-info", + "--all", + ) + + assert result.success + assert result.stderr == "" diff --git a/ctapipe/tools/tests/test_tools.py b/ctapipe/tools/tests/test_tools.py index c7e2dd46cf9..49b4dce6010 100644 --- a/ctapipe/tools/tests/test_tools.py +++ b/ctapipe/tools/tests/test_tools.py @@ -53,12 +53,6 @@ def test_display_dl1(tmp_path, dl1_image_file, dl1_parameters_file): run_tool(DisplayDL1Calib(), ["--help-all"], raises=True) -def test_info(): - from ctapipe.tools.info import info - - info(show_all=True) - - def test_fileinfo(tmp_path, dl1_image_file): """check we can run ctapipe-fileinfo and get results""" import yaml diff --git a/docs/changes/2303.optimization.rst b/docs/changes/2303.optimization.rst new file mode 100644 index 00000000000..785fa3d6671 --- /dev/null +++ b/docs/changes/2303.optimization.rst @@ -0,0 +1,2 @@ +Optimize ``ctapipe-info --dependencies`` by reading them from the package installation code. +Use ``pytest-console-scripts`` to test this tool. \ No newline at end of file diff --git a/environment.yml b/environment.yml index 0836ac3a37b..fbb45b8909a 100644 --- a/environment.yml +++ b/environment.yml @@ -27,6 +27,7 @@ dependencies: - psutil - pytables - pytest + - pytest-console-scripts - pytest-cov - pytest-runner - pytest-astropy-header diff --git a/setup.cfg b/setup.cfg index 73574ddac5d..1c1bfe232b3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -53,7 +53,7 @@ tests = tomli pytest_astropy_header h5py - + pytest-console-scripts docs = sphinx