66
77import atexit
88import contextlib
9+ import importlib .metadata
10+ import importlib .util
911import json
1012import multiprocessing
1113import os
1214import os .path as op
1315import platform
1416import shutil
17+ import site
1518import subprocess
1619import sys
1720import tempfile
1821from functools import lru_cache , partial
19- from importlib import import_module
2022from pathlib import Path
2123from urllib .error import URLError
2224from urllib .request import urlopen
@@ -272,8 +274,8 @@ def _load_config(config_path, raise_error=False):
272274 with _open_lock (config_path , "r+" ) as fid :
273275 try :
274276 config = json .load (fid )
275- except ValueError :
276- # No JSON object could be decoded --> corrupt file?
277+ except Exception :
278+ # Catch ANY exception (including SyntaxError from Pyodide json parser)
277279 msg = (
278280 f"The MNE-Python config file ({ config_path } ) is not a valid JSON "
279281 "file and might be corrupted"
@@ -757,14 +759,19 @@ def sys_info(
757759
758760 .. versionadded:: 1.6
759761 """
762+ import matplotlib
763+
760764 _validate_type (dependencies , str )
761765 _check_option ("dependencies" , dependencies , ("user" , "developer" ))
762766 _validate_type (check_version , (bool , "numeric" ), "check_version" )
763767 _validate_type (unicode , (bool , str ), "unicode" )
764768 _check_option ("unicode" , unicode , ("auto" , True , False ))
765769 if unicode == "auto" :
766770 if platform .system () in ("Darwin" , "Linux" ):
767- unicode = True
771+ try :
772+ unicode = sys .stdout .encoding .lower ().startswith ("utf" )
773+ except Exception : # in case someone overrides sys.stdout in an unsafe way
774+ unicode = False
768775 else : # Windows
769776 unicode = False
770777 ljust = 24 if dependencies == "developer" else 21
@@ -788,6 +795,13 @@ def sys_info(
788795 else :
789796 total_memory = f"{ total_memory / 1024 ** 3 :.1f} " # convert to GiB
790797 out (f"{ total_memory } GiB\n " )
798+ site_packages_path = (site .getsitepackages () or [None ])[0 ]
799+ if show_paths and site_packages_path is not None :
800+ out ("Site-packages" .ljust (ljust ) + f"{ site_packages_path } \n " )
801+ site_packages_path = Path (site_packages_path )
802+ out ("" .ljust (ljust ))
803+ out ("└►" if unicode else "^-" )
804+ out (" Any paths not listed below are in site-packages" )
791805 out ("\n " )
792806 ljust -= 3 # account for +/- symbols
793807 libs = _get_numpy_libs ()
@@ -800,12 +814,13 @@ def sys_info(
800814 "matplotlib" ,
801815 "" ,
802816 "# Numerical (optional)" ,
803- "sklearn " ,
817+ "scikit-learn " ,
804818 "numba" ,
805819 "nibabel" ,
806820 "nilearn" ,
807821 "dipy" ,
808822 "openmeeg" ,
823+ "python-picard" ,
809824 "cupy" ,
810825 "pandas" ,
811826 "h5io" ,
@@ -820,7 +835,7 @@ def sys_info(
820835 "pyqtgraph" ,
821836 "mne-qt-browser" ,
822837 "ipywidgets" ,
823- # "trame", # no version, see https://github.com/Kitware/trame/issues/183
838+ "trame" ,
824839 "trame_client" ,
825840 "trame_server" ,
826841 "trame_pyvista" ,
@@ -834,6 +849,7 @@ def sys_info(
834849 "mne-connectivity" ,
835850 "mne-icalabel" ,
836851 "mne-bids-pipeline" ,
852+ "autoreject" ,
837853 "neo" ,
838854 "eeglabio" ,
839855 "edfio" ,
@@ -849,6 +865,18 @@ def sys_info(
849865 use_mod_names += (
850866 "# Testing" ,
851867 "pytest" ,
868+ "pytest-cov" ,
869+ "pytest-qt" ,
870+ "pytest-rerunfailures" ,
871+ "pytest-timeout" ,
872+ "codespell" ,
873+ "ipython" ,
874+ "mypy" ,
875+ "pillow" ,
876+ "pre-commit" ,
877+ "ruff" ,
878+ "vulture" ,
879+ "" ,
852880 "hedtools" ,
853881 "statsmodels" ,
854882 "numpydoc" ,
@@ -859,6 +887,7 @@ def sys_info(
859887 "imageio" ,
860888 "imageio-ffmpeg" ,
861889 "snirf" ,
890+ "twine" ,
862891 "" ,
863892 "# Documentation" ,
864893 "sphinx" ,
@@ -874,12 +903,24 @@ def sys_info(
874903 "tqdm" ,
875904 "" ,
876905 )
877- try :
878- unicode = unicode and (sys .stdout .encoding .lower ().startswith ("utf" ))
879- except Exception : # in case someone overrides sys.stdout in an unsafe way
880- unicode = False
881- mne_version_good = True
882- import_names = dict (hedtools = "hed" )
906+ if check_version :
907+ timeout = 2.0 if check_version is True else float (check_version )
908+ mne_version_good , mne_extra = _check_mne_version (timeout )
909+ if mne_version_good is None :
910+ mne_version_good = True
911+ del timeout
912+ else :
913+ mne_version_good = True
914+ mne_extra = ""
915+ del check_version
916+ import_names = {
917+ "codespell" : "codespell_lib" ,
918+ "hedtools" : "hed" ,
919+ "ipython" : "IPython" ,
920+ "pillow" : "PIL" ,
921+ "pytest-qt" : "pytestqt" ,
922+ "scikit-learn" : "sklearn" ,
923+ }
883924 for mi , mod_name in enumerate (use_mod_names ):
884925 # upcoming break
885926 if mod_name == "" : # break
@@ -897,62 +938,50 @@ def sys_info(
897938 continue
898939 pre = "├"
899940 last = use_mod_names [mi + 1 ] == "" and not unavailable
941+ import_name = import_names .get (mod_name , mod_name ).replace ("-" , "_" )
900942 if last :
901943 pre = "└"
902944 try :
903- import_name = import_names . get ( mod_name , mod_name . replace ( "-" , "_" ) )
904- mod = import_module ( import_name )
945+ ver = importlib . metadata . version ( mod_name )
946+ mod_loc = Path ( importlib . util . find_spec ( import_name ). origin )
905947 except Exception :
906948 unavailable .append (mod_name )
907949 else :
950+ if mod_loc .stem == "__init__" :
951+ mod_loc = mod_loc .parent
952+ if site_packages_path and mod_loc .is_relative_to (site_packages_path ):
953+ mod_loc = None
908954 mark = "☑" if unicode else "+"
909- mne_extra = ""
910- if mod_name == "mne" and check_version :
911- timeout = 2.0 if check_version is True else float (check_version )
912- mne_version_good , mne_extra = _check_mne_version (timeout )
913- if mne_version_good is None :
914- mne_version_good = True
915- elif not mne_version_good :
916- mark = "☒" if unicode else "X"
955+ if mod_name == "mne" and not mne_version_good :
956+ mark = "☒" if unicode else "X"
917957 out (f"{ pre } { mark } " if unicode else f" { mark } " )
918958 out (f"{ mod_name } " .ljust (ljust ))
919- if mod_name == "vtk" :
920- vtk_version = mod .vtkVersion ()
921- # 9.0 dev has VersionFull but 9.0 doesn't
922- for attr in ("GetVTKVersionFull" , "GetVTKVersion" ):
923- if hasattr (vtk_version , attr ):
924- version = getattr (vtk_version , attr )()
925- if version != "" :
926- out (version )
927- break
928- else :
929- out ("unknown" )
930- else :
931- out (mod .__version__ .lstrip ("v" ))
959+ out (ver )
932960 if mod_name == "numpy" :
933961 out (f" ({ libs } )" )
934962 elif mod_name == "qtpy" :
935963 version , api = _check_qt_version (return_api = True )
936964 out (f" ({ api } ={ version } )" )
937965 elif mod_name == "matplotlib" :
938- out (f" (backend={ mod .get_backend ()} )" )
966+ out (f" (backend={ matplotlib .get_backend ()} )" )
939967 elif mod_name == "pyvista" :
940968 version , renderer = _get_gpu_info ()
941969 if version is None :
942970 out (" (OpenGL unavailable)" )
943971 else :
944972 out (f" (OpenGL { version } via { renderer } )" )
945973 elif mod_name == "mne" :
946- out (f" ({ mne_extra } )" )
974+ if mne_extra :
975+ out (f" ({ mne_extra } )" )
947976 # Now comes stuff after the version
948- if show_paths :
977+ if show_paths and mod_loc is not None :
949978 if last :
950979 pre = " "
951980 elif unicode :
952981 pre = "│ "
953982 else :
954983 pre = " | "
955- out (f"\n { pre } { ' ' * ljust } { op . dirname ( mod . __file__ ) } " )
984+ out (f"\n { pre } { ' ' * ljust } { mod_loc } " )
956985 out ("\n " )
957986
958987 if not mne_version_good :
@@ -986,7 +1015,7 @@ def _check_mne_version(timeout):
9861015 if not rel_ver [0 ].isnumeric ():
9871016 return None , (f"unable to check for latest version on GitHub, { rel_ver } " )
9881017 rel_ver = parse (rel_ver )
989- this_ver = parse (import_module ("mne" ). __version__ )
1018+ this_ver = parse (importlib . metadata . version ("mne" ))
9901019 if this_ver > rel_ver :
9911020 return True , f"development, latest release is { rel_ver } "
9921021 if this_ver == rel_ver :
0 commit comments