15

My directory layout is as follows

project\
project\setup.py
project\scripts\foo.py
project\scripts\bar.py
project\scripts\__init__.py
project\tests\test_foo.py
project\tests\__init__.py

My test file looks as follows

project\tests\test_fo.py

from ..scripts import foo

def test_one():
     assert 0

I get the following error, when I do

cd C:\project
C:\virtualenvs\test_env\Scripts\activate
python setup.py install
python setup.py test

E ValueError: Attempted relative import beyond toplevel package

What am I doing wrong? This is my setup.py

setup(
    name = 'project',
    setup_requires=['pytest-runner'],
    tests_require=['pytest'],
    packages = ["scripts","tests"],
    package_data={
          'scripts': ['*.py'],
          'tests': ['*.py'],
         },
)
user330612
  • 2,189
  • 7
  • 33
  • 64

1 Answers1

13

Relative imports only work within a package. scripts may be a package, and so is tests, but the project directory is not (and neither should it be). This makes scripts and tests top-level packages. You can't refer to other top-level names using relative syntax.

Moreover, tests are not run with the tests package; the test runner imports the test_foo module, not the tests.test_foo module, so as far as Python is concerned test_foo is a top-level module.

scripts is a top-level name, just use that directly. You will have to add the project directory to sys.path however. You can do so at the top of your test_foo.py file with:

import os
import sys

TEST_DIR = os.path.dirname(os.path.abspath(__file__))
PROJECT_DIR = os.path.abspath(os.path.join(TEST_DIR, os.pardir))
sys.path.insert(0, PROJECT_DIR)

then import from scripts with absolute paths:

from scripts import foo

Note however that when you run python setup.py then your current working directory is added to sys.path anyway, so scripts is available directly without having to fiddle with sys.path.

Moreover, pytest will already do the work for you; for any given test file it'll make sure the first parent directory with no __init__.py file in it is on sys.path. In your case that's the project/ directory, so again scripts is directly available to import from. See Good Practices:

If pytest finds a “a/b/test_module.py” test file while recursing into the filesystem it determines the import name as follows:

  • determine basedir: this is the first “upward” (towards the root) directory not containing an __init__.py. If e.g. both a and b contain an __init__.py file then the parent directory of a will become the basedir.
  • perform sys.path.insert(0, basedir) to make the test module importable under the fully qualified import name.
  • import a.b.test_module where the path is determined by converting path separators / into ”.” characters. This means you must follow the convention of having directory and file names map directly to the import names.

Note that in order to actually use pytest to run your tests when you use setup.py test, you need to register an alias in your setup.cfg file (create it in project/ if you do not have one):

[aliases]
test = pytest
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • after I change it to from scripts import foo I get this error (test_123) D:\Projects\project>python setup.py test running test running egg_info writing project.egg-info\PKG-INFO writing top-level names to project.egg-info\top_level.txt writing dependency_links to project.egg-info\dependency_links.txt reading manifest file 'project.egg-info\SOURCES.txt' writing manifest file 'project.egg-info\SOURCES.txt' running build_ext ---------------------------------------------------------------------- Ran 0 tests in 0.000s OK – user330612 Jul 06 '16 at 19:47
  • @user330612: I'll take a look tomorrow. – Martijn Pieters Jul 06 '16 at 21:41
  • @user330612: do note the Good Practices link in my answer though; there is info there on having to add an alias to the `setup.cfg` file before `pytest` is used to run the tests. – Martijn Pieters Jul 06 '16 at 21:42
  • @user330612: without `setup.cfg` setting the alias, no tests are run, with the alias, pytest discovers the tests in `tests` and runs them. – Martijn Pieters Jul 07 '16 at 06:47
  • I don't get it where should i put this code? is it in `conftest.py`? – Ali Husham Oct 08 '21 at 08:15
  • 1
    @alial-karaawi: the point is that you don't need to do that, not when you use pytest. – Martijn Pieters Oct 29 '21 at 12:36