vitaut
vitaut

Reputation: 55655

Why does import work with Python nose when test directory contains __init__.py?

Consider the following project structure:

a.py
test/
  test_a.py

with test_a.py importing module a:

import a

As expected, running nosetests in the test directory results in import error:

ERROR: Failure: ImportError (No module named a)

However, I noticed that adding an empty __init__.py file to the test directory makes import work with nosetests (but not when you run test_a.py with Python). Could you explain why?

I understand that adding __init__.py makes test a package. But does it mean that import includes the directory containing the package in the lookup?

Upvotes: 6

Views: 1481

Answers (2)

Mir
Mir

Reputation: 680

I looked into the souce code of nose module and here's why.

def importFromPath(self, path, fqname):
    """Import a dotted-name package whose tail is at path. In other words,
    given foo.bar and path/to/foo/bar.py, import foo from path/to/foo then
    bar from path/to/foo/bar, returning bar.
    """
    # find the base dir of the package
    path_parts = os.path.normpath(os.path.abspath(path)).split(os.sep)
    name_parts = fqname.split('.')
    if path_parts[-1] == '__init__.py':
        path_parts.pop()
    path_parts = path_parts[:-(len(name_parts))]
    dir_path = os.sep.join(path_parts)
    # then import fqname starting from that dir
    return self.importFromDir(dir_path, fqname)

def importFromDir(self, dir, fqname):
    """Import a module *only* from path, ignoring sys.path and
    reloading if the version in sys.modules is not the one we want.
    """
    dir = os.path.normpath(os.path.abspath(dir))

In your case when importFromDir is called from importFromPath, 'dir' is the directory a level above from the __init__.py directory. So that's why adding __init__.py to your test makes 'import a' work

Upvotes: 2

wim
wim

Reputation: 363294

The presence of an __init__.py file in the directory transforms test from just a plain old directory into a python package. This has an effect on sys.path.

Modify your test_a.py module like this:

import sys

def test_thing():
    for i, p in enumerate(sys.path):
        print i, p

try:
    import a
except ImportError:
    print('caught import error')

Then try running nosetests -s from the test directory, with and without an __init__.py in there.

Note: it is the test runner that munges sys.path. And that is documented in the second "Note" of this section here (thanks @davidism). You won't see any change there just by running python test_a.py with and without the package structure.

Upvotes: 5

Related Questions