Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add sys_info utility to display dependencies version #122

Merged
merged 3 commits into from
Sep 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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

Check warning on line 1 in pycrostates/commands/sys_info.py

View check run for this annotation

Codecov / codecov/patch

pycrostates/commands/sys_info.py#L1

Added line #L1 was not covered by tests

from .. import sys_info

Check warning on line 3 in pycrostates/commands/sys_info.py

View check run for this annotation

Codecov / codecov/patch

pycrostates/commands/sys_info.py#L3

Added line #L3 was not covered by tests


def run():

Check warning on line 6 in pycrostates/commands/sys_info.py

View check run for this annotation

Codecov / codecov/patch

pycrostates/commands/sys_info.py#L6

Added line #L6 was not covered by tests
"""Run sys_info() command."""
parser = argparse.ArgumentParser(

Check warning on line 8 in pycrostates/commands/sys_info.py

View check run for this annotation

Codecov / codecov/patch

pycrostates/commands/sys_info.py#L8

Added line #L8 was not covered by tests
prog=f"{__package__.split('.')[0]}-sys_info", description="sys_info"
)
parser.add_argument(

Check warning on line 11 in pycrostates/commands/sys_info.py

View check run for this annotation

Codecov / codecov/patch

pycrostates/commands/sys_info.py#L11

Added line #L11 was not covered by tests
"--developer",
help="display information for optional dependencies",
action="store_true",
)
args = parser.parse_args()

Check warning on line 16 in pycrostates/commands/sys_info.py

View check run for this annotation

Codecov / codecov/patch

pycrostates/commands/sys_info.py#L16

Added line #L16 was not covered by tests

sys_info(developer=args.developer)

Check warning on line 18 in pycrostates/commands/sys_info.py

View check run for this annotation

Codecov / codecov/patch

pycrostates/commands/sys_info.py#L18

Added line #L18 was not covered by tests
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

Check warning on line 68 in pycrostates/utils/sys_info.py

View check run for this annotation

Codecov / codecov/patch

pycrostates/utils/sys_info.py#L68

Added line #L68 was not covered by tests
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

Check warning on line 84 in pycrostates/utils/sys_info.py

View check run for this annotation

Codecov / codecov/patch

pycrostates/utils/sys_info.py#L84

Added line #L84 was not covered by tests
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"

Check warning on line 106 in pycrostates/utils/sys_info.py

View check run for this annotation

Codecov / codecov/patch

pycrostates/utils/sys_info.py#L105-L106

Added lines #L105 - L106 were not covered by tests

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")

Check warning on line 121 in pycrostates/utils/sys_info.py

View check run for this annotation

Codecov / codecov/patch

pycrostates/utils/sys_info.py#L121

Added line #L121 was not covered by tests
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