Skip to content

Commit

Permalink
Add sys_info utility to display dependencies version (#122)
Browse files Browse the repository at this point in the history
* add sys_info utility

* fix import

* fix style
  • Loading branch information
mscheltienne authored Sep 14, 2023
1 parent a8416cc commit 45659fa
Show file tree
Hide file tree
Showing 8 changed files with 187 additions and 3 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,20 @@ jobs:
python -m pip install --progress-bar off --upgrade pip setuptools wheel
python -m pip install --progress-bar off .[build]
- name: Test package install
run: python -c "import pycrostates; print(pycrostates.__version__)"
run: pycrostates-sys_info
- name: Remove package install
run: python -m pip uninstall -y pycrostates
- name: Build package
run: python -m build
- name: Install sdist
run: pip install ./dist/*.tar.gz
- name: Test sdist install
run: python -c "import pycrostates; print(pycrostates.__version__)"
run: pycrostates-sys_info
- name: Remove sdist install
run: python -m pip uninstall -y pycrostates
- name: Install wheel
run: pip install ./dist/*.whl
- name: Test wheel install
run: python -c "import pycrostates; print(pycrostates.__version__)"
run: pycrostates-sys_info
- name: Remove wheel install
run: python -m pip uninstall -y pycrostates
2 changes: 2 additions & 0 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ stages:
python -m pip install --progress-bar off --upgrade --pre --only-binary :all: -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple --timeout=180 numpy scipy scikit-learn matplotlib
condition: eq(variables.PRE, 'true')
displayName: 'Install pre-release dependencies'
- script: pycrostates-sys_info --developer
displayName: 'Display Pycrostates config'
- script: mne sys_info -pd
displayName: 'Display MNE config'
- script: |
Expand Down
2 changes: 2 additions & 0 deletions pycrostates/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from . import cluster, datasets, metrics, preprocessing, utils, viz
from ._version import __version__ # noqa: F401
from .utils._logs import set_log_level
from .utils.sys_info import sys_info # noqa: F401

__all__ = (
"cluster",
Expand All @@ -12,4 +13,5 @@
"utils",
"viz",
"set_log_level",
"sys_info",
)
Empty file.
18 changes: 18 additions & 0 deletions pycrostates/commands/sys_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import argparse

from .. import sys_info


def run():
"""Run sys_info() command."""
parser = argparse.ArgumentParser(
prog=f"{__package__.split('.')[0]}-sys_info", description="sys_info"
)
parser.add_argument(
"--developer",
help="display information for optional dependencies",
action="store_true",
)
args = parser.parse_args()

sys_info(developer=args.developer)
121 changes: 121 additions & 0 deletions pycrostates/utils/sys_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import platform
import sys
from functools import partial
from importlib.metadata import requires, version
from typing import IO, Callable, List, Optional

import psutil
from packaging.requirements import Requirement

from ._checks import _check_type


def sys_info(fid: Optional[IO] = None, developer: bool = False):
"""Print the system information for debugging.
Parameters
----------
fid : file-like | None
The file to write to, passed to :func:`print`. Can be None to use
:data:`sys.stdout`.
developer : bool
If True, display information about optional dependencies.
"""
_check_type(developer, (bool,), "developer")

ljust = 26
out = partial(print, end="", file=fid)
package = __package__.split(".")[0]

# OS information - requires python 3.8 or above
out("Platform:".ljust(ljust) + platform.platform() + "\n")
# python information
out("Python:".ljust(ljust) + sys.version.replace("\n", " ") + "\n")
out("Executable:".ljust(ljust) + sys.executable + "\n")
# CPU information
out("CPU:".ljust(ljust) + platform.processor() + "\n")
out("Physical cores:".ljust(ljust) + str(psutil.cpu_count(False)) + "\n")
out("Logical cores:".ljust(ljust) + str(psutil.cpu_count(True)) + "\n")
# memory information
out("RAM:".ljust(ljust))
out(f"{psutil.virtual_memory().total / float(2 ** 30):0.1f} GB\n")
out("SWAP:".ljust(ljust))
out(f"{psutil.swap_memory().total / float(2 ** 30):0.1f} GB\n")
# package information
out(f"{package}:".ljust(ljust) + version(package) + "\n")

# dependencies
out("\nCore dependencies\n")
dependencies = [Requirement(elt) for elt in requires(package)]
core_dependencies = [dep for dep in dependencies if "extra" not in str(dep.marker)]
_list_dependencies_info(out, ljust, package, core_dependencies)

# extras
if developer:
keys = (
"build",
"docs",
"test",
"style",
)
for key in keys:
extra_dependencies = [
dep
for dep in dependencies
if all(elt in str(dep.marker) for elt in ("extra", key))
]
if len(extra_dependencies) == 0:
continue
out(f"\nOptional '{key}' dependencies\n")
_list_dependencies_info(out, ljust, package, extra_dependencies)


def _list_dependencies_info(
out: Callable, ljust: int, package: str, dependencies: List[Requirement]
):
"""List dependencies names and versions."""
unicode = sys.stdout.encoding.lower().startswith("utf")
if unicode:
ljust += 1

not_found: List[Requirement] = list()
for dep in dependencies:
if dep.name == package:
continue
try:
version_ = version(dep.name)
except Exception:
not_found.append(dep)
continue

# build the output string step by step
output = f"✔︎ {dep.name}" if unicode else dep.name
# handle version specifiers
if len(dep.specifier) != 0:
output += f" ({str(dep.specifier)})"
output += ":"
output = output.ljust(ljust) + version_

# handle special dependencies with backends, C dep, ..
if dep.name in ("matplotlib", "seaborn") and version_ != "Not found.":
try:
from matplotlib import pyplot as plt

backend = plt.get_backend()
except Exception:
backend = "Not found"

output += f" (backend: {backend})"
out(output + "\n")

if len(not_found) != 0:
not_found = [
f"{dep.name} ({str(dep.specifier)})"
if len(dep.specifier) != 0
else dep.name
for dep in not_found
]
if unicode:
out(f"✘ Not installed: {', '.join(not_found)}\n")
else:
out(f"Not installed: {', '.join(not_found)}\n")
36 changes: 36 additions & 0 deletions pycrostates/utils/tests/test_sys_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""Test config.py"""

from io import StringIO

from pycrostates.utils.sys_info import sys_info


def test_sys_info():
"""Test info-showing utility."""
out = StringIO()
sys_info(fid=out)
value = out.getvalue()
out.close()
assert "Platform:" in value
assert "Executable:" in value
assert "CPU:" in value
assert "Physical cores:" in value
assert "Logical cores" in value
assert "RAM:" in value
assert "SWAP:" in value

assert "numpy" in value
assert "psutil" in value

assert "style" not in value
assert "test" not in value

out = StringIO()
sys_info(fid=out, developer=True)
value = out.getvalue()
out.close()

assert "build" in value
assert "docs" in value
assert "style" in value
assert "test" in value
5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ dependencies = [
'matplotlib',
'mne>=1.2',
'numpy>=1.21',
'packaging',
'pooch',
'psutil',
'scikit-learn',
'scipy',
]
Expand Down Expand Up @@ -95,6 +97,9 @@ documentation = 'https://pycrostates.readthedocs.io/en/master/'
source = 'https://github.com/vferat/pycrostates'
tracker = 'https://github.com/vferat/pycrostates/issues'

[project.scripts]
pycrostates-sys_info = 'pycrostates.commands.sys_info:run'

[tool.setuptools]
include-package-data = false

Expand Down

0 comments on commit 45659fa

Please sign in to comment.