0

I am needing to defer the import of some libraries unless they are needed so I have created have an object to defer the import.

class Importer(object):
    def __init__(self, name):
        self.name = name

    def __bool__(self):
        try:
            mod = importlib.import_module(self.name)
            return True
        except ImportError:
            return False

    def __getattr__(self, name):
        try:
            mod = importlib.import_module(self.name)
            attr = getattr(mod, name)
            setattr(self, name, attr)
            return attr
        except ImportError as e:
            msg = self.name + " must be installed"
            raise ImportError(msg)

I have run nose with my tests and everything passes. However, when I run --with-doctest it runs an extra test (that I have not defined) that generates an error in any environment where a given library isn't installed.

Testing the code below recreates the issue I am experiencing. In an environment without numpy, nosetests --with-doctest is running a second test which I have not defined.

numpy = Importer('numpy')

def mean(array):
    """
    Mean of an array.

    Examples
    --------
    >>> mean([1, 2, 3])
    2.0
    >>> mean([1, -1])
    0.0
    """
    if numpy:
        return numpy.mean(array)
    return float(sum(array) / len(array))

Clearly, there is only one doctest available to test. Also if numpy is installed only 1 test is run. So why then when numpy is not installed am I getting a second test? Here is the output.

.E
======================================================================
ERROR: Failure: ImportError (numpy must be installed)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/devin/Dropbox/Devin_S_+_Spark_Wave/nimble/zissue/issue.py", line 16, in __getattr__
    mod = importlib.import_module(self.name)
  File "/Users/devin/anaconda3/envs/issueEnv/lib/python3.6/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 994, in _gcd_import
  File "<frozen importlib._bootstrap>", line 971, in _find_and_load
  File "<frozen importlib._bootstrap>", line 953, in _find_and_load_unlocked
ModuleNotFoundError: No module named 'numpy'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/devin/anaconda3/envs/issueEnv/lib/python3.6/site-packages/nose/failure.py", line 39, in runTest
    raise self.exc_val.with_traceback(self.tb)
  File "/Users/devin/anaconda3/envs/issueEnv/lib/python3.6/site-packages/nose/plugins/manager.py", line 154, in generate
    for r in result:
  File "/Users/devin/anaconda3/envs/issueEnv/lib/python3.6/site-packages/nose/plugins/doctests.py", line 228, in loadTestsFromModule
    tests = self.finder.find(module)
  File "/Users/devin/anaconda3/envs/issueEnv/lib/python3.6/doctest.py", line 933, in find
    self._find(tests, obj, name, module, source_lines, globs, {})
  File "/Users/devin/anaconda3/envs/issueEnv/lib/python3.6/doctest.py", line 992, in _find
    if ((inspect.isroutine(inspect.unwrap(val))
  File "/Users/devin/anaconda3/envs/issueEnv/lib/python3.6/inspect.py", line 512, in unwrap
    while _is_wrapper(func):
  File "/Users/devin/anaconda3/envs/issueEnv/lib/python3.6/inspect.py", line 503, in _is_wrapper
    return hasattr(f, '__wrapped__')
  File "/Users/devin/Dropbox/testing/issue/issue.py", line 22, in __getattr__
    raise ImportError(msg)
ImportError: numpy must be installed

----------------------------------------------------------------------
Ran 2 tests in 0.003s

1 Answer 1

0

Well, this is fun!

The core difficulty is that the doctest module "tricked" you into attempting a numpy import. That is, it probed for numpy.__wrapped__, and you took the bait. You need to distinguish between a probe call, for which returning a dummy value is acceptable, and a real call, for which you must import or raise.

Perhaps a heuristic of testing for __wrapped__ suffices. But you may find it necessary to inspect the call stack to find the caller, notice it is e.g. autodoc, and change behavior based on that.

1
  • Ah yes, that makes sense! Thanks for the suggestions as well.
    – dshanahan
    Commented Oct 26, 2019 at 16:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.