66
77import atexit
88import json
9+ import multiprocessing
910import os
1011import os .path as op
1112import platform
1516import sys
1617import tempfile
1718from functools import partial
19+ from importlib import import_module
1820from pathlib import Path
1921
2022from .check import (_validate_type , _check_qt_version , _check_option ,
@@ -500,56 +502,26 @@ def _get_gpu_info():
500502 return out
501503
502504
503- def sys_info (fid = None , show_paths = False , * , dependencies = 'user' ):
504- """Print the system information for debugging .
505+ def sys_info (fid = None , show_paths = False , * , dependencies = 'user' , unicode = True ):
506+ """Print system information.
505507
506- This function is useful for printing system information
507- to help triage bugs.
508+ This function prints system information useful when triaging bugs.
508509
509510 Parameters
510511 ----------
511512 fid : file-like | None
512- The file to write to. Will be passed to :func:`print()`.
513- Can be None to use :data:`sys.stdout`.
513+ The file to write to. Will be passed to :func:`print()`. Can be None to
514+ use :data:`sys.stdout`.
514515 show_paths : bool
515516 If True, print paths for each module.
516517 dependencies : 'user' | 'developer'
517518 Show dependencies relevant for users (default) or for developers
518519 (i.e., output includes additional dependencies).
520+ unicode : bool
521+ Include Unicode symbols in output.
519522
520523 .. versionadded:: 0.24
521-
522- Examples
523- --------
524- Running this function with no arguments prints an output that is
525- useful when submitting bug reports::
526-
527- >>> import mne
528- >>> mne.sys_info() # doctest: +SKIP
529- Platform: Linux-4.15.0-1067-aws-x86_64-with-glibc2.2.5
530- Python: 3.8.1 (default, Feb 2 2020, 08:37:37) [GCC 8.3.0]
531- Executable: /usr/local/bin/python
532- CPU: : 36 cores
533- Memory: 68.7 GB
534-
535- mne: 0.21.dev0
536- numpy: 1.19.0 {blas=openblas, lapack=openblas}
537- scipy: 1.5.1
538- matplotlib: 3.2.2 {backend=Qt5Agg}
539-
540- sklearn: 0.23.1
541- numba: 0.50.1
542- nibabel: 3.1.1
543- nilearn: 0.7.0
544- dipy: 1.1.1
545- cupy: Not found
546- pandas: 1.0.5
547- pyvista: 0.25.3 {pyvistaqt=0.1.1, OpenGL 3.3 (Core Profile) Mesa 18.3.6 via llvmpipe (LLVM 7.0, 256 bits)}
548- vtk: 9.0.1
549- qtpy: 2.0.1 {PySide6=6.2.4}
550- pyqtgraph: 0.12.4
551- pooch: v1.5.1
552- """ # noqa: E501
524+ """
553525 _validate_type (dependencies , str )
554526 _check_option ('dependencies' , dependencies , ('user' , 'developer' ))
555527 ljust = 21 if dependencies == 'developer' else 18
@@ -569,14 +541,8 @@ def sys_info(fid=None, show_paths=False, *, dependencies='user'):
569541 out ('Platform:' .ljust (ljust ) + platform_str + '\n ' )
570542 out ('Python:' .ljust (ljust ) + str (sys .version ).replace ('\n ' , ' ' ) + '\n ' )
571543 out ('Executable:' .ljust (ljust ) + sys .executable + '\n ' )
572- out ('CPU:' .ljust (ljust ) + f'{ platform .processor ()} : ' )
573- try :
574- import multiprocessing
575- except ImportError :
576- out ('number of processors unavailable '
577- '(requires "multiprocessing" package)\n ' )
578- else :
579- out (f'{ multiprocessing .cpu_count ()} cores\n ' )
544+ out ('CPU:' .ljust (ljust ) + f'{ platform .processor ()} ' )
545+ out (f'({ multiprocessing .cpu_count ()} cores)\n ' )
580546 out ('Memory:' .ljust (ljust ))
581547 try :
582548 import psutil
@@ -586,26 +552,63 @@ def sys_info(fid=None, show_paths=False, *, dependencies='user'):
586552 out (f'{ psutil .virtual_memory ().total / float (2 ** 30 ):0.1f} GB\n ' )
587553 out ('\n ' )
588554 libs = _get_numpy_libs ()
589- use_mod_names = ('mne' , 'numpy' , 'scipy' , 'matplotlib' , '' , 'sklearn' ,
590- 'numba' , 'nibabel' , 'nilearn' , 'dipy' , 'openmeeg' , 'cupy' ,
591- 'pandas' , 'pyvista' , 'pyvistaqt' , 'ipyvtklink' , 'vtk' ,
592- 'qtpy' , 'ipympl' , 'pyqtgraph' , 'pooch' , '' , 'mne_bids' ,
593- 'mne_nirs' , 'mne_features' , 'mne_qt_browser' ,
594- 'mne_connectivity' , 'mne_icalabel' )
555+ unavailable = []
556+ use_mod_names = (
557+ '# Core' ,
558+ 'mne' , 'numpy' , 'scipy' , 'matplotlib' , 'pooch' , 'jinja2' ,
559+ '' ,
560+ '# Numerical (optional)' ,
561+ 'sklearn' , 'numba' , 'nibabel' , 'nilearn' , 'dipy' , 'openmeeg' , 'cupy' ,
562+ 'pandas' ,
563+ '' ,
564+ '# Visualization (optional)' ,
565+ 'pyvista' , 'pyvistaqt' , 'ipyvtklink' , 'vtk' , 'qtpy' , 'ipympl' ,
566+ 'pyqtgraph' , 'mne-qt-browser' ,
567+ '' ,
568+ '# Ecosystem (optional)' ,
569+ 'mne_bids' , 'mne_nirs' , 'mne_features' , 'mne_connectivity' ,
570+ 'mne_icalabel' ,
571+ ''
572+ )
595573 if dependencies == 'developer' :
596574 use_mod_names += (
597- '' , 'sphinx' , 'sphinx_gallery' , 'numpydoc' , 'pydata_sphinx_theme' ,
598- 'pytest' , 'nbclient' )
599- for mod_name in use_mod_names :
600- if mod_name == '' :
601- out ('\n ' )
575+ '# Testing' ,
576+ 'pytest' , 'nbclient' , 'numpydoc' , 'flake8' , 'pydocstyle' ,
577+ '' ,
578+ '# Documentation' ,
579+ 'sphinx' , 'sphinx_gallery' , 'pydata_sphinx_theme' ,
580+ '' ,
581+ )
582+ try :
583+ unicode = unicode and (sys .stdout .encoding .lower ().startswith ('utf' ))
584+ except Exception : # in case someone overrides sys.stdout in an unsafe way
585+ unicode = False
586+ for mi , mod_name in enumerate (use_mod_names ):
587+ # upcoming break
588+ if mod_name == '' : # break
589+ if unavailable :
590+ out ('└☐ ' if unicode else ' - ' )
591+ out ('unavailable:' .ljust (ljust ))
592+ out (f"{ ', ' .join (unavailable )} \n " )
593+ unavailable = []
594+ if mi != len (use_mod_names ) - 1 :
595+ out ('\n ' )
596+ continue
597+ elif mod_name .startswith ('# ' ): # header
598+ mod_name = mod_name .replace ('# ' , '' )
599+ out (f'{ mod_name } \n ' )
602600 continue
603- out (f'{ mod_name } :' .ljust (ljust ))
601+ pre = '├'
602+ last = use_mod_names [mi + 1 ] == '' and not unavailable
603+ if last :
604+ pre = '└'
604605 try :
605- mod = __import__ (mod_name )
606+ mod = import_module (mod_name )
606607 except Exception :
607- out ( 'Not found \n ' )
608+ unavailable . append ( mod_name )
608609 else :
610+ out (f'{ pre } ☑ ' if unicode else ' + ' )
611+ out (f'{ mod_name } :' .ljust (ljust ))
609612 if mod_name == 'vtk' :
610613 vtk_version = mod .vtkVersion ()
611614 # 9.0 dev has VersionFull but 9.0 doesn't
@@ -618,20 +621,26 @@ def sys_info(fid=None, show_paths=False, *, dependencies='user'):
618621 else :
619622 out ('unknown' )
620623 else :
621- out (mod .__version__ )
624+ out (mod .__version__ . lstrip ( "v" ) )
622625 if mod_name == 'numpy' :
623- out (f' {{ { libs } }} ' )
626+ out (f' ( { libs } ) ' )
624627 elif mod_name == 'qtpy' :
625628 version , api = _check_qt_version (return_api = True )
626- out (f' {{ { api } ={ version } }} ' )
629+ out (f' ( { api } ={ version } ) ' )
627630 elif mod_name == 'matplotlib' :
628- out (f' {{ backend={ mod .get_backend ()} }} ' )
631+ out (f' ( backend={ mod .get_backend ()} ) ' )
629632 elif mod_name == 'pyvista' :
630633 version , renderer = _get_gpu_info ()
631634 if version is None :
632- out (' { OpenGL could not be initialized} ' )
635+ out (' ( OpenGL unavailable) ' )
633636 else :
634- out (f' {{ OpenGL { version } via { renderer } }} ' )
637+ out (f' ( OpenGL { version } via { renderer } ) ' )
635638 if show_paths :
636- out (f'\n { " " * ljust } •{ op .dirname (mod .__file__ )} ' )
639+ if last :
640+ pre = ' '
641+ elif unicode :
642+ pre = '│ '
643+ else :
644+ pre = ' | '
645+ out (f'\n { pre } { " " * ljust } { op .dirname (mod .__file__ )} ' )
637646 out ('\n ' )
0 commit comments