Source code for spectrochempy.utils.print_versions

# ======================================================================================
# Copyright (©) 2015-2025 LCS - Laboratoire Catalyse et Spectrochimie, Caen, France.
# CeCILL-B FREE SOFTWARE LICENSE AGREEMENT
# See full LICENSE agreement in the root directory.
# ======================================================================================
# ruff: noqa: S602, S603

"""Utility functions for printing version information."""

import contextlib
import locale
import platform
import re
import struct
import subprocess
import sys
from importlib.metadata import distributions
from importlib.metadata import requires
from importlib.metadata import version
from os import environ
from pathlib import Path

__all__ = ["show_versions"]


[docs] def show_versions(file=sys.stdout): """ Print the versions of spectrochempy and its dependencies. Parameters ---------- file : file-like, optional Print to the given file-like object. Defaults to sys.stdout. """ underlined_title("SYSTEM INFO", "=", file=file) for key, val in _get_sys_info(): print(f"- {key: <15} {val}", file=file) env = get_environment_info() underlined_title("ENVIRONMENT INFO", "=", file=file) for k, v in env.items(): print(f"- {k: <15} {v}", file=file) underlined_title("SPECTROCHEMPY", "=", file=file) vers = version("spectrochempy") print(f"{'- version': <15} {vers}", file=file) underlined_title("INSTALLED PACKAGES", "=", file=file) # dependencies # Load project metadata req = requires("spectrochempy") req = [r.split("; extra == ") for r in req] deps = [r[0] for r in req if len(r) == 1] opt_deps = {} for r in req: if len(r) == 1: continue if len(r) == 2: r[1] = r[1].strip('"') opt = opt_deps.get(r[1], []) opt.append(r[0]) opt_deps[r[1]] = opt # Get installed packages installed = get_installed_versions() # display results of comparison with the requirements underlined_title("Dependencies", file=file) underlined_title( f"{'Package': <20} {'Required': <25} {'Installed': <15}", ".", ret=False, file=file, ) strg = check_dependencies(deps, opt_deps, installed) print(strg, file=file)
# import json # print(json.dumps(dict(sorted(installed.items())), indent=4)) def underlined_title(s, char="-", file=sys.stdout, ret=True): """ Print an underlined title. Parameters ---------- s : str The title string. char : str, optional The character to use for underlining. Defaults to '-'. file : file-like, optional Print to the given file-like object. Defaults to sys.stdout. ret : bool, optional Whether to add a newline before the title. Defaults to True. """ n = "\n" if ret else "" print(n + s, file=file) print(char * len(s), file=file) def _get_sys_info(): """ Return system information as a list of tuples. Returns ------- list of tuples System information. """ # copied from XArray REPOS = Path(__file__).parent.parent.parent blob = [] # get full commit hash commit = None if (REPOS / ".git").is_dir() and REPOS.is_dir(): try: git_executable = str(Path(sys.executable).parent / "git") if not Path(git_executable).is_file(): raise FileNotFoundError(f"Git executable not found: {git_executable}") result = subprocess.run( # noqa: S603 [ git_executable, "log", "--format=%H", "-n", "1", ], capture_output=True, check=True, text=True, ) commit = result.stdout.strip() except Exception: # noqa: S110 pass else: if result.returncode == 0: commit = result.stdout with contextlib.suppress(ValueError): commit = result.stdout commit = commit.strip().strip('"') blob.append(("commit", commit)) with contextlib.suppress(Exception): (sysname, _nodename, release, _version, machine, processor) = platform.uname() blob.extend( [ ("python", sys.version), ("python-bits", struct.calcsize("P") * 8), ("OS", f"{sysname}"), ("OS-release", f"{release}"), ("machine", f"{machine}"), ("processor", f"{processor}"), ("byteorder", f"{sys.byteorder}"), ("LC_ALL", f"{environ.get('LC_ALL', 'None')}"), ("LANG", f"{environ.get('LANG', 'None')}"), ("LOCALE", f"{locale.getlocale()}"), ], ) return blob def check_dependencies(deps, other_deps, installed): """ Compare installed versions with requirements. Parameters ---------- deps : list List of core dependencies. other_deps : dict Dictionary of optional dependencies. installed : dict Dictionary of installed packages and their versions. Returns ------- str Formatted string of dependency comparison results. """ # make a dictionary of package and version requirements requirements = {"core": deps, **other_deps} for key, deps in requirements.items(): new_deps = {} for package in deps: # change eventual "==" to "=" package = re.sub("==", "=", package).strip() # split version for compare in ("<=", ">=", "=", "@"): if compare not in package: continue pkg, version = package.split(compare, maxsplit=1) if compare == "@": version = version.strip() version = version[0:4] + "..." + version[-17:] version = compare + version break else: pkg = package version = "Any" new_deps[pkg] = version requirements[key] = new_deps # compare with installed packages strg = "" for key, deps in requirements.items(): strg += f"\n---- {key} ----\n" for pkg, req_ver in deps.items(): inst_ver = installed.get(pkg, "Not installed") strg += f"{pkg: <20} {req_ver: <25} {inst_ver: <15}\n" return strg def get_user_directory(): """ Get user home directory path. Returns ------- str User home directory path. """ return str(Path.home()) def get_environment_info(): """ Detect virtual environment type and return info. Returns ------- dict Dictionary of environment information. """ env_info = {} user_dir = get_user_directory() # Check for conda environment if environ.get("CONDA_DEFAULT_ENV") or environ.get("CONDA_PREFIX"): env_info["type"] = "conda" env_info["name"] = environ.get("CONDA_DEFAULT_ENV", "unknown") env_info["prefix"] = environ.get("CONDA_PREFIX", "unknown").replace( user_dir, "~", ) # Check for pip virtual environment elif sys.prefix != sys.base_prefix: env_info["type"] = "venv" env_info["name"] = environ.get("VIRTUAL_ENV", "").split("/")[-1] env_info["prefix"] = str(Path(sys.prefix)) # .relative_to(user_dir)) else: env_info["type"] = "system" env_info["name"] = "none" env_info["prefix"] = str(Path(sys.prefix)) return env_info def get_installed_versions(): """ Get installed packages and their versions using importlib.metadata. Returns ------- dict Dictionary of installed packages and their versions. """ installed = {} for dist in distributions(): with contextlib.suppress(Exception): # Skip packages with invalid metadata installed[dist.metadata["Name"].lower()] = dist.version return installed if __name__ == "__main__": # pragma: no cover show_versions()