diff --git a/doc/release/upcoming_changes/26255.improvement.rst b/doc/release/upcoming_changes/26255.improvement.rst new file mode 100644 index 000000000000..bbb970fd81e8 --- /dev/null +++ b/doc/release/upcoming_changes/26255.improvement.rst @@ -0,0 +1,5 @@ +`np.show_runtime` has display modes +----------------------------------- + +`np.show_runtime` now has a new optional parameter ``mode`` to help +customize the output. diff --git a/numpy/__config__.py.in b/numpy/__config__.py.in index ce224e49a15d..a9b97771aae1 100644 --- a/numpy/__config__.py.in +++ b/numpy/__config__.py.in @@ -1,33 +1,21 @@ # This file is generated by numpy's build process # It contains system_info results at the time of building this package. -from enum import Enum from numpy._core._multiarray_umath import ( __cpu_features__, __cpu_baseline__, __cpu_dispatch__, ) +from numpy._utils._config_helpers import ( + ConfigDisplayModes, + cleanup_empty_dict_values, + print_or_return_config, +) __all__ = ["show"] _built_with_meson = True -class DisplayModes(Enum): - stdout = "stdout" - dicts = "dicts" - - -def _cleanup(d): - """ - Removes empty values in a `dict` recursively - This ensures we remove values that Meson could not provide to CONFIG - """ - if isinstance(d, dict): - return {k: _cleanup(v) for k, v in d.items() if v and _cleanup(v)} - else: - return d - - -CONFIG = _cleanup( +CONFIG = cleanup_empty_dict_values( { "Compilers": { "c": { @@ -109,13 +97,7 @@ CONFIG = _cleanup( ) -def _check_pyyaml(): - import yaml - - return yaml - - -def show(mode=DisplayModes.stdout.value): +def show(mode=ConfigDisplayModes.stdout.value): """ Show libraries and system information on which NumPy was built and is being used @@ -136,6 +118,7 @@ def show(mode=DisplayModes.stdout.value): -------- get_include : Returns the directory containing NumPy C header files. + show_runtime : Print information about various resources in the system Notes ----- @@ -143,20 +126,4 @@ def show(mode=DisplayModes.stdout.value): output if ``pyyaml`` is installed """ - if mode == DisplayModes.stdout.value: - try: # Non-standard library, check import - yaml = _check_pyyaml() - - print(yaml.dump(CONFIG)) - except ModuleNotFoundError: - import warnings - import json - - warnings.warn("Install `pyyaml` for better output", stacklevel=1) - print(json.dumps(CONFIG, indent=2)) - elif mode == DisplayModes.dicts.value: - return CONFIG - else: - raise AttributeError( - f"Invalid `mode`, use one of: {', '.join([e.value for e in DisplayModes])}" - ) + return print_or_return_config(mode, CONFIG) diff --git a/numpy/__init__.pyi b/numpy/__init__.pyi index 2e08ddbb1791..c5935f819c7b 100644 --- a/numpy/__init__.pyi +++ b/numpy/__init__.pyi @@ -13,6 +13,7 @@ from contextlib import contextmanager import numpy as np from numpy._pytesttester import PytestTester from numpy._core._internal import _ctypes +from numpy._utils._config_helpers import ConfigDisplayModes from numpy._typing import ( # Arrays @@ -637,7 +638,7 @@ test: PytestTester # # Placeholders for classes -def show_config() -> None: ... +def show_config(mode: ConfigDisplayModes) -> None | dict: ... _NdArraySubClass = TypeVar("_NdArraySubClass", bound=NDArray[Any]) _DTypeScalar_co = TypeVar("_DTypeScalar_co", covariant=True, bound=generic) diff --git a/numpy/_utils/_config_helpers.py b/numpy/_utils/_config_helpers.py new file mode 100644 index 000000000000..231646679fc2 --- /dev/null +++ b/numpy/_utils/_config_helpers.py @@ -0,0 +1,48 @@ +from enum import Enum + + +class ConfigDisplayModes(Enum): + stdout = "stdout" + dicts = "dicts" + + +def cleanup_empty_dict_values(d): + """ + Removes empty values in a `dict` recursively + This ensures we remove values that Meson could not provide to CONFIG + """ + if isinstance(d, dict): + return { + k: cleanup_empty_dict_values(v) + for k, v in d.items() + if v and cleanup_empty_dict_values(v) + } + else: + return d + + +def check_pyyaml(): + import yaml + + return yaml + + +def print_or_return_config(mode, config): + if mode == ConfigDisplayModes.stdout.value: + try: # Non-standard library, check import + yaml = check_pyyaml() + + print(yaml.dump(config)) + except ModuleNotFoundError: + import warnings + import json + + warnings.warn("Install `pyyaml` for better output", stacklevel=1) + print(json.dumps(config, indent=2)) + elif mode == ConfigDisplayModes.dicts.value: + return config + else: + raise AttributeError( + "Invalid `mode`, use one of: " + f"{', '.join([e.value for e in ConfigDisplayModes])}" + ) diff --git a/numpy/lib/_utils_impl.py b/numpy/lib/_utils_impl.py index 0c5d08ee7d9c..1b05b75e234f 100644 --- a/numpy/lib/_utils_impl.py +++ b/numpy/lib/_utils_impl.py @@ -8,7 +8,7 @@ import platform from numpy._core import ndarray -from numpy._utils import set_module +from numpy._utils import set_module, _config_helpers import numpy as np __all__ = [ @@ -17,7 +17,7 @@ @set_module('numpy') -def show_runtime(): +def show_runtime(mode=_config_helpers.ConfigDisplayModes.stdout.value): """ Print information about various resources in the system including available intrinsic support and BLAS/LAPACK library @@ -25,6 +25,18 @@ def show_runtime(): .. versionadded:: 1.24.0 + Parameters + ---------- + mode : {`'stdout'`, `'dicts'`}, optional. + Indicates how to display the config information. + `'stdout'` prints to console, `'dicts'` returns a dictionary + of the configuration. + + Returns + ------- + out : {`dict`, `None`} + If mode is `'dicts'`, a dict is returned, else None + See Also -------- show_config : Show libraries in the system on which NumPy was built. @@ -41,11 +53,19 @@ def show_runtime(): __cpu_features__, __cpu_baseline__, __cpu_dispatch__ ) from pprint import pprint + uname = platform.uname() config_found = [{ "numpy_version": np.__version__, "python": sys.version, - "uname": platform.uname(), - }] + "uname": { + "machine": uname.machine, + "node": uname.node, + "processor": uname.processor, + "release": uname.release, + "system": uname.system, + "version": uname.version, + }, + }] features_found, features_not_found = [], [] for feature in __cpu_dispatch__: if __cpu_features__[feature]: @@ -67,8 +87,7 @@ def show_runtime(): " Install it by `pip install threadpoolctl`." " Once installed, try `np.show_runtime` again" " for more detailed build information") - pprint(config_found) - + return _config_helpers.print_or_return_config(mode, config_found) @set_module('numpy') def get_include(): diff --git a/numpy/lib/_utils_impl.pyi b/numpy/lib/_utils_impl.pyi index b1453874e85e..8bf2b47d2118 100644 --- a/numpy/lib/_utils_impl.pyi +++ b/numpy/lib/_utils_impl.pyi @@ -7,6 +7,7 @@ from typing import ( from numpy._core.numerictypes import ( issubdtype as issubdtype, ) +from numpy._utils._config_helpers import ConfigDisplayModes _T_contra = TypeVar("_T_contra", contravariant=True) @@ -30,4 +31,4 @@ def source( output: None | _SupportsWrite[str] = ..., ) -> None: ... -def show_runtime() -> None: ... +def show_runtime(mode: ConfigDisplayModes) -> None | dict: ... diff --git a/numpy/tests/test_numpy_config.py b/numpy/tests/test_numpy_config.py index 82c1ad70b930..d4b9f0f14731 100644 --- a/numpy/tests/test_numpy_config.py +++ b/numpy/tests/test_numpy_config.py @@ -18,7 +18,7 @@ class TestNumPyConfigs: "Python Information", ] - @patch("numpy.__config__._check_pyyaml") + @patch("numpy._utils._config_helpers.check_pyyaml") def test_pyyaml_not_found(self, mock_yaml_importer): mock_yaml_importer.side_effect = ModuleNotFoundError() with pytest.warns(UserWarning): @@ -38,7 +38,7 @@ def test_invalid_mode(self): np.show_config(mode="foo") def test_warn_to_add_tests(self): - assert len(np.__config__.DisplayModes) == 2, ( + assert len(np._utils._config_helpers.ConfigDisplayModes) == 2, ( "New mode detected," " please add UT if applicable and increment this count" )