"""
This module provides decorator functions which can be applied to test objects
in order to skip those objects when certain conditions occur. A sample use case
is to detect if the platform is missing ``matplotlib``. If so, any test objects
which require ``matplotlib`` and decorated with ``@td.skip_if_no_mpl`` will be
skipped by ``pytest`` during the execution of the test suite.
To illustrate, after importing this module:
import pandas.util._test_decorators as td
The decorators can be applied to classes:
@td.skip_if_some_reason
class Foo():
...
Or individual functions:
@td.skip_if_some_reason
def test_foo():
...
For more information, refer to the ``pytest`` documentation on ``skipif``.
"""
from distutils.version import LooseVersion
import locale
import pytest
from pandas.compat import (
PY3, import_lzma, is_platform_32bit, is_platform_windows)
from pandas.compat.numpy import _np_version_under1p15
from pandas.core.computation.expressions import (
_NUMEXPR_INSTALLED, _USE_NUMEXPR)
def safe_import(mod_name, min_version=None):
"""
Parameters:
-----------
mod_name : str
Name of the module to be imported
min_version : str, default None
Minimum required version of the specified mod_name
Returns:
--------
object
The imported module if successful, or False
"""
try:
mod = __import__(mod_name)
except ImportError:
return False
if not min_version:
return mod
else:
import sys
try:
version = getattr(sys.modules[mod_name], '__version__')
except AttributeError:
# xlrd uses a capitalized attribute name
version = getattr(sys.modules[mod_name], '__VERSION__')
if version:
from distutils.version import LooseVersion
if LooseVersion(version) >= LooseVersion(min_version):
return mod
return False
def _skip_if_no_mpl():
mod = safe_import("matplotlib")
if mod:
mod.use("Agg", warn=False)
else:
return True
def _skip_if_mpl_2_2():
mod = safe_import("matplotlib")
if mod:
v = mod.__version__
if LooseVersion(v) > LooseVersion('2.1.2'):
return True
else:
mod.use("Agg", warn=False)
def _skip_if_has_locale():
lang, _ = locale.getlocale()
if lang is not None:
return True
def _skip_if_not_us_locale():
lang, _ = locale.getlocale()
if lang != 'en_US':
return True
def _skip_if_no_scipy():
return not (safe_import('scipy.stats') and
safe_import('scipy.sparse') and
safe_import('scipy.interpolate') and
safe_import('scipy.signal'))
def _skip_if_no_lzma():
try:
import_lzma()
except ImportError:
return True
def skip_if_no(package, min_version=None):
"""
Generic function to help skip test functions when required packages are not
present on the testing system.
Intended for use as a decorator, this function will wrap the decorated
function with a pytest ``skip_if`` mark. During a pytest test suite
execution, that mark will attempt to import the specified ``package`` and
optionally ensure it meets the ``min_version``. If the import and version
check are unsuccessful, then the decorated function will be skipped.
Parameters
----------
package: str
The name of the package required by the decorated function
min_version: str or None, default None
Optional minimum version of the package required by the decorated
function
Returns
-------
decorated_func: function
The decorated function wrapped within a pytest ``skip_if`` mark
"""
def decorated_func(func):
msg = "Could not import '{}'".format(package)
if min_version:
msg += " satisfying a min_version of {}".format(min_version)
return pytest.mark.skipif(
not safe_import(package, min_version=min_version), reason=msg
)(func)
return decorated_func
skip_if_no_mpl = pytest.mark.skipif(_skip_if_no_mpl(),
reason="Missing matplotlib dependency")
skip_if_np_lt_115 = pytest.mark.skipif(_np_version_under1p15,
reason="NumPy 1.15 or greater required")
skip_if_mpl = pytest.mark.skipif(not _skip_if_no_mpl(),
reason="matplotlib is present")
xfail_if_mpl_2_2 = pytest.mark.xfail(_skip_if_mpl_2_2(),
reason="matplotlib 2.2",
strict=False)
skip_if_32bit = pytest.mark.skipif(is_platform_32bit(),
reason="skipping for 32 bit")
skip_if_windows = pytest.mark.skipif(is_platform_windows(),
reason="Running on Windows")
skip_if_windows_python_3 = pytest.mark.skipif(is_platform_windows() and PY3,
reason=("not used on python3/"
"win32"))
skip_if_has_locale = pytest.mark.skipif(_skip_if_has_locale(),
reason="Specific locale is set {lang}"
.format(lang=locale.getlocale()[0]))
skip_if_not_us_locale = pytest.mark.skipif(_skip_if_not_us_locale(),
reason="Specific locale is set "
"{lang}".format(
lang=locale.getlocale()[0]))
skip_if_no_scipy = pytest.mark.skipif(_skip_if_no_scipy(),
reason="Missing SciPy requirement")
skip_if_no_lzma = pytest.mark.skipif(_skip_if_no_lzma(),
reason="need backports.lzma to run")
skip_if_no_ne = pytest.mark.skipif(not _USE_NUMEXPR,
reason="numexpr enabled->{enabled}, "
"installed->{installed}".format(
enabled=_USE_NUMEXPR,
installed=_NUMEXPR_INSTALLED))
def parametrize_fixture_doc(*args):
"""
Intended for use as a decorator for parametrized fixture,
this function will wrap the decorated function with a pytest
``parametrize_fixture_doc`` mark. That mark will format
initial fixture docstring by replacing placeholders {0}, {1} etc
with parameters passed as arguments.
Parameters:
----------
args: iterable
Positional arguments for docstring.
Returns:
-------
documented_fixture: function
The decorated function wrapped within a pytest
``parametrize_fixture_doc`` mark
"""
def documented_fixture(fixture):
fixture.__doc__ = fixture.__doc__.format(*args)
return fixture
return documented_fixture