1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373
|
# -*- coding: utf-8 -*-
"""Some utility functions."""
# Authors: Alexandre Gramfort <alexandre.gramfort@inria.fr>
#
# License: BSD (3-clause)
import contextlib
import inspect
from io import StringIO
import re
import sys
import logging
import os.path as op
import warnings
from ..fixes import _get_args
from ..externals.decorator import FunctionMaker
logger = logging.getLogger('mne') # one selection here used across mne-python
logger.propagate = False # don't propagate (in case of multiple imports)
def verbose(function):
"""Verbose decorator to allow functions to override log-level.
Parameters
----------
function : callable
Function to be decorated by setting the verbosity level.
Returns
-------
dec : callable
The decorated function
Examples
--------
You can use the ``verbose`` argument to set the verbose level on the fly::
>>> import mne
>>> cov = mne.compute_raw_covariance(raw, verbose='WARNING') # doctest: +SKIP
>>> cov = mne.compute_raw_covariance(raw, verbose='INFO') # doctest: +SKIP
Using up to 49 segments
Number of samples used : 5880
[done]
See Also
--------
set_log_level
set_config
Notes
-----
This decorator is used to set the verbose level during a function or method
call, such as :func:`mne.compute_covariance`. The `verbose` keyword
argument can be 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL', True (an
alias for 'INFO'), or False (an alias for 'WARNING'). To set the global
verbosity level for all functions, use :func:`mne.set_log_level`.
This function also serves as a docstring filler.
""" # noqa: E501
# See https://decorator.readthedocs.io/en/latest/tests.documentation.html
# #dealing-with-third-party-decorators
from .docs import fill_doc
arg_names = _get_args(function)
try:
fill_doc(function)
except TypeError: # nothing to add
pass
def wrapper(*args, **kwargs):
default_level = verbose_level = None
if len(arg_names) > 0 and arg_names[0] == 'self':
default_level = getattr(args[0], 'verbose', None)
if 'verbose' in kwargs:
verbose_level = kwargs.pop('verbose')
else:
try:
verbose_level = args[arg_names.index('verbose')]
except (IndexError, ValueError):
pass
# This ensures that object.method(verbose=None) will use object.verbose
if verbose_level is None:
verbose_level = default_level
if verbose_level is not None:
# set it back if we get an exception
with use_log_level(verbose_level):
return function(*args, **kwargs)
return function(*args, **kwargs)
return FunctionMaker.create(
function, 'return decfunc(%(signature)s)',
dict(decfunc=wrapper), __wrapped__=function,
__qualname__=function.__qualname__)
class use_log_level(object):
"""Context handler for logging level.
Parameters
----------
level : int
The level to use.
"""
def __init__(self, level): # noqa: D102
self.level = level
def __enter__(self): # noqa: D105
self.old_level = set_log_level(self.level, True)
def __exit__(self, *args): # noqa: D105
set_log_level(self.old_level)
def set_log_level(verbose=None, return_old_level=False):
"""Set the logging level.
Parameters
----------
verbose : bool, str, int, or None
The verbosity of messages to print. If a str, it can be either DEBUG,
INFO, WARNING, ERROR, or CRITICAL. Note that these are for
convenience and are equivalent to passing in logging.DEBUG, etc.
For bool, True is the same as 'INFO', False is the same as 'WARNING'.
If None, the environment variable MNE_LOGGING_LEVEL is read, and if
it doesn't exist, defaults to INFO.
return_old_level : bool
If True, return the old verbosity level.
"""
from .config import get_config
if verbose is None:
verbose = get_config('MNE_LOGGING_LEVEL', 'INFO')
elif isinstance(verbose, bool):
if verbose is True:
verbose = 'INFO'
else:
verbose = 'WARNING'
if isinstance(verbose, str):
verbose = verbose.upper()
logging_types = dict(DEBUG=logging.DEBUG, INFO=logging.INFO,
WARNING=logging.WARNING, ERROR=logging.ERROR,
CRITICAL=logging.CRITICAL)
if verbose not in logging_types:
raise ValueError('verbose must be of a valid type')
verbose = logging_types[verbose]
logger = logging.getLogger('mne')
old_verbose = logger.level
logger.setLevel(verbose)
return (old_verbose if return_old_level else None)
def set_log_file(fname=None, output_format='%(message)s', overwrite=None):
"""Set the log to print to a file.
Parameters
----------
fname : str, or None
Filename of the log to print to. If None, stdout is used.
To suppress log outputs, use set_log_level('WARN').
output_format : str
Format of the output messages. See the following for examples:
https://docs.python.org/dev/howto/logging.html
e.g., "%(asctime)s - %(levelname)s - %(message)s".
overwrite : bool | None
Overwrite the log file (if it exists). Otherwise, statements
will be appended to the log (default). None is the same as False,
but additionally raises a warning to notify the user that log
entries will be appended.
"""
logger = logging.getLogger('mne')
handlers = logger.handlers
for h in handlers:
# only remove our handlers (get along nicely with nose)
if isinstance(h, (logging.FileHandler, logging.StreamHandler)):
if isinstance(h, logging.FileHandler):
h.close()
logger.removeHandler(h)
if fname is not None:
if op.isfile(fname) and overwrite is None:
# Don't use warn() here because we just want to
# emit a warnings.warn here (not logger.warn)
warnings.warn('Log entries will be appended to the file. Use '
'overwrite=False to avoid this message in the '
'future.', RuntimeWarning, stacklevel=2)
overwrite = False
mode = 'w' if overwrite else 'a'
lh = logging.FileHandler(fname, mode=mode)
else:
""" we should just be able to do:
lh = logging.StreamHandler(sys.stdout)
but because doctests uses some magic on stdout, we have to do this:
"""
lh = logging.StreamHandler(WrapStdOut())
lh.setFormatter(logging.Formatter(output_format))
# actually add the stream handler
logger.addHandler(lh)
class catch_logging(object):
"""Store logging.
This will remove all other logging handlers, and return the handler to
stdout when complete.
"""
def __enter__(self): # noqa: D105
self._data = StringIO()
self._lh = logging.StreamHandler(self._data)
self._lh.setFormatter(logging.Formatter('%(message)s'))
self._lh._mne_file_like = True # monkey patch for warn() use
for lh in logger.handlers:
logger.removeHandler(lh)
logger.addHandler(self._lh)
return self._data
def __exit__(self, *args): # noqa: D105
logger.removeHandler(self._lh)
set_log_file(None)
class WrapStdOut(object):
"""Dynamically wrap to sys.stdout.
This makes packages that monkey-patch sys.stdout (e.g.doctest,
sphinx-gallery) work properly.
"""
def __getattr__(self, name): # noqa: D105
# Even more ridiculous than this class, this must be sys.stdout (not
# just stdout) in order for this to work (tested on OSX and Linux)
if hasattr(sys.stdout, name):
return getattr(sys.stdout, name)
else:
raise AttributeError("'file' object has not attribute '%s'" % name)
_verbose_dec_re = re.compile('^<.*decorator\\.py:decorator-gen.*>$')
def warn(message, category=RuntimeWarning, module='mne'):
"""Emit a warning with trace outside the mne namespace.
This function takes arguments like warnings.warn, and sends messages
using both ``warnings.warn`` and ``logger.warn``. Warnings can be
generated deep within nested function calls. In order to provide a
more helpful warning, this function traverses the stack until it
reaches a frame outside the ``mne`` namespace that caused the error.
Parameters
----------
message : str
Warning message.
category : instance of Warning
The warning class. Defaults to ``RuntimeWarning``.
module : str
The name of the module emitting the warning.
"""
import mne
root_dir = op.dirname(mne.__file__)
frame = None
if logger.level <= logging.WARN:
last_fname = ''
frame = inspect.currentframe()
while frame:
fname = frame.f_code.co_filename
lineno = frame.f_lineno
# in verbose dec
if _verbose_dec_re.match(fname) and \
last_fname == op.basename(__file__):
last_fname = fname
frame = frame.f_back
continue
# treat tests as scripts
# and don't capture unittest/case.py (assert_raises)
if not (fname.startswith(root_dir) or
('unittest' in fname and 'case' in fname)) or \
op.basename(op.dirname(fname)) == 'tests':
break
last_fname = op.basename(fname)
frame = frame.f_back
del frame
# We need to use this instead of warn(message, category, stacklevel)
# because we move out of the MNE stack, so warnings won't properly
# recognize the module name (and our warnings.simplefilter will fail)
warnings.warn_explicit(
message, category, fname, lineno, module,
globals().get('__warningregistry__', {}))
# To avoid a duplicate warning print, we only emit the logger.warning if
# one of the handlers is a FileHandler. See gh-5592
if any(isinstance(h, logging.FileHandler) or getattr(h, '_mne_file_like',
False)
for h in logger.handlers):
logger.warning(message)
def filter_out_warnings(warn_record, category=None, match=None):
r"""Remove particular records from ``warn_record``.
This helper takes a list of :class:`warnings.WarningMessage` objects,
and remove those matching category and/or text.
Parameters
----------
category: WarningMessage type | None
class of the message to filter out
match : str | None
text or regex that matches the error message to filter out
Examples
--------
This can be used as::
>>> import pytest
>>> import warnings
>>> from mne.utils import filter_out_warnings
>>> with pytest.warns(None) as recwarn:
... warnings.warn("value must be 0 or None", UserWarning)
>>> filter_out_warnings(recwarn, match=".* 0 or None")
>>> assert len(recwarn.list) == 0
>>> with pytest.warns(None) as recwarn:
... warnings.warn("value must be 42", UserWarning)
>>> filter_out_warnings(recwarn, match=r'.* must be \d+$')
>>> assert len(recwarn.list) == 0
>>> with pytest.warns(None) as recwarn:
... warnings.warn("this is not here", UserWarning)
>>> filter_out_warnings(recwarn, match=r'.* must be \d+$')
>>> assert len(recwarn.list) == 1
"""
regexp = re.compile('.*' if match is None else match)
is_category = [w.category == category if category is not None else True
for w in warn_record._list]
is_match = [regexp.match(w.message.args[0]) is not None
for w in warn_record._list]
ind = [ind for ind, (c, m) in enumerate(zip(is_category, is_match))
if c and m]
for i in reversed(ind):
warn_record._list.pop(i)
class ETSContext(object):
"""Add more meaningful message to errors generated by ETS Toolkit."""
def __enter__(self): # noqa: D105
pass
def __exit__(self, type, value, traceback): # noqa: D105
if isinstance(value, SystemExit) and value.code.\
startswith("This program needs access to the screen"):
value.code += ("\nThis can probably be solved by setting "
"ETS_TOOLKIT=qt4. On bash, type\n\n $ export "
"ETS_TOOLKIT=qt4\n\nand run the command again.")
@contextlib.contextmanager
def wrapped_stdout(indent=''):
"""Wrap stdout writes to logger.info, with an optional indent prefix."""
orig_stdout = sys.stdout
my_out = StringIO()
sys.stdout = my_out
try:
yield
finally:
sys.stdout = orig_stdout
for line in my_out.getvalue().split('\n'):
logger.info(indent + line)
|