diff --git a/src/pyhf/cli/cli.py b/src/pyhf/cli/cli.py index a1a486fe54..88d4f07a1c 100644 --- a/src/pyhf/cli/cli.py +++ b/src/pyhf/cli/cli.py @@ -4,9 +4,9 @@ import click from pyhf import __version__ -from pyhf.cli import rootio, spec, infer, patchset, complete +from pyhf.cli import complete, infer, patchset, rootio, spec, utils from pyhf.contrib import cli as contrib -from pyhf import utils +from pyhf.utils import citation logging.basicConfig() log = logging.getLogger(__name__) @@ -15,7 +15,7 @@ def _print_citation(ctx, param, value): if not value or ctx.resilient_parsing: return - click.echo(utils.citation()) + click.echo(citation()) ctx.exit() @@ -53,6 +53,8 @@ def pyhf(): pyhf.add_command(patchset.cli) +pyhf.add_command(utils.cli) + pyhf.add_command(complete.cli) pyhf.add_command(contrib.cli) diff --git a/src/pyhf/cli/utils.py b/src/pyhf/cli/utils.py new file mode 100644 index 0000000000..7dc5a2a978 --- /dev/null +++ b/src/pyhf/cli/utils.py @@ -0,0 +1,35 @@ +"""The pyhf utils CLI subcommand.""" +import logging +from pathlib import Path + +import click + +from pyhf import utils + +log = logging.getLogger(__name__) + + +@click.group(name="utils") +def cli(): + """Utils CLI group.""" + + +@cli.command() +@click.option( + "-o", + "--output-file", + help="The location of the output file. If not specified, prints to screen.", + default=None, +) +def environment(output_file): + environment_info = utils.environment_info() + + if output_file: + output_file = Path(output_file) + output_file.parent.mkdir(parents=True, exist_ok=True) + + with open(output_file, "w+", encoding="utf-8") as out_file: + out_file.write(environment_info) + log.environment(f"Written to {output_file}") + else: + click.echo(environment_info) diff --git a/src/pyhf/utils.py b/src/pyhf/utils.py index 859f887913..49cf5147bc 100644 --- a/src/pyhf/utils.py +++ b/src/pyhf/utils.py @@ -1,10 +1,13 @@ -import json -import yaml -import click import hashlib +import json +import platform +import sys from gettext import gettext -import sys +import click +import yaml + +from pyhf import __version__ # importlib.resources.as_file wasn't added until Python 3.9 # c.f. https://docs.python.org/3.9/library/importlib.html#importlib.resources.as_file @@ -17,6 +20,7 @@ "EqDelimStringParamType", "citation", "digest", + "environment_info", "options_from_eqdelimstring", ] @@ -126,3 +130,57 @@ def citation(oneline=False): if oneline: data = ''.join(data.splitlines()) return data + + +def environment_info(): + """ + Produce OS / environment information useful for filing a bug report + + Example: + + >>> import pyhf + >>> pyhf.utils.environment_info() + + Returns: + os_info (:obj:`str`): The operating system and environment information + for the host machine. + """ + + os_version = "Cannot be determined" + if sys.platform == "linux": + try: + # platform.freedesktop_os_release added in Python 3.10 + # Remove when Python 3.9 support dropped + from platform import freedesktop_os_release + except ImportError: + # c.f. https://docs.python.org/3/library/platform.html#platform.freedesktop_os_release + from pathlib import Path + + def freedesktop_os_release(): + os_release_path = Path("/etc") / "os-release" + if os_release_path.exists(): + with open(os_release_path, encoding="utf8") as read_file: + os_release_file = read_file.read() + os_release_list = os_release_file.split("\n") + # Remove all trailing lines + os_release_list = list(filter(("").__ne__, os_release_list)) + return { + token.split("=")[0]: token.split("=")[1].replace('"', '') + for token in os_release_list + } + else: + raise OSError + + try: + os_release = freedesktop_os_release() + os_version = f"{os_release['NAME']} {os_release['VERSION']}" + except OSError: + os_version = "Cannot be determined" + elif sys.platform == "darwin": + os_version = f"macOS {platform.mac_ver()[0]}" + + os_info = f"* os version: {os_version}\n" + kernel_info = f"* kernel version: {platform.system()} {platform.release()} {platform.machine()}\n" + python_info = f"* python version: {platform.python_implementation()} {platform.python_version()} [{platform.python_compiler()}]\n" + pyhf_version = f"* pyhf version: {__version__}\n" + return os_info + kernel_info + python_info + pyhf_version