views:

64

answers:

3

Hi. I have a Python project with following directory structure:

/(some files)
/model/(python files)
/tools/(more python files)
...

So, I have Python files in couple subdirectories and there are some dependencies between directories as well: tools are used by model, etc. Now my problem is that I want to make doctests for both models and tools, and I want be able to run tests from command line like this: ./model/car.py . I can make this work, but only with messy boilerplate code. I would like to know what is the correct way, or is there any?

Question: How should I write my imports?

Thanx. Here is an example...

Content of tools/tool.py:

#!/usr/bin/env python
"""
   >>> is_four(21)
   False
   >>> is_four(4)
   True
"""

def is_four(val):
    return val == 4

if __name__ == '__main__':
    import doctest
    doctest.testmod()

... and model/car.py:

#!/usr/bin/env python   
"""
   >>> car = Car()
   >>> car.ok()
   True
"""

from tools.tool import *

class Car(object):
    def __init__(self):
        self.tire_count = 4
    def ok(self):
        return is_four(self.tire_count)

if __name__ == '__main__':
    import doctest
    doctest.testmod()

By adding following lines in the begin of car.py it works, but doesn't look nice. :(

if __name__ == '__main__':
    import sys
    import os
    sys.path.append(os.path.abspath(os.path.dirname('..')))
+1  A: 

Use packages. Add an __init__.py file to your working directory and all subfolders then your imports will search the parent directories if it doesn't find the module in the current directory.

See http://www.network-theory.co.uk/docs/pytut/Packages.html

Also this question is a duplicate of:

http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder

Pierre-Antoine LaFayette
Actually, I just tried that, and relative import as well, and it doesn't seem to solve the OP problem. Funny that such a simple question can be so puzzling...
e-satis
A: 

Don't frob sys.path in this manner. Instead either use $PYTHONPATH to force the base directory in when invoking python, or use python -m model.car from that directory.

Ignacio Vazquez-Abrams
That doesn't seem right. You can't setup the env to fit a software needs, or you would have trouble for deployments.
e-satis
@e-satis: With deployments the proper entries would already be in `sys.path` and you wouldn't need to touch the env.
Ignacio Vazquez-Abrams
When your user will want to test your module, you will give them the choice between running python -m for each dir of your app or installing them in the site-packages ?
e-satis
@e-satis: Testing is not something most users will be expected to do, so having them jump through a bit of a hoop... is not really something I'd be concerned with. If enough complain then toss them a shell script or batch file.
Ignacio Vazquez-Abrams
Honestly, when I take time to test a module, if it doesn't run out of the box with nose, I skip to the next one...
e-satis
This works but it still requires me to run test scripts with *PYTHONPATH=$PWD python model/car.py* or *python -m model/car model/car.py* but not with *./model/car.py*.(The production system have of course sys.path set up correctly.)
sankari
A: 

What you are trying to do is a relative import. It works fine in Python, but on the module level, not on the file system level. I know, this is confusing.

It means that if you run a script in a subdir, it doesn't see the upper dirs because for the running script, the root of the module is the current dir: there is no upper module.

So what are relative imports for?

Well, module in subdirs car import module in upper dirs as long as they are themself imported from a upperdir.

In your case it means you must run your scripts from "/" so it becomes the root of the module, and the submodules are allowed to use relative import.

A possible solution to your problem is to remove your if __name__ == "__main__" block and create /tests.py:

import doctest
from model import car
from tools import tool

doctest.testmod(car)
doctest.testmod(tool)

Then run in too launch all the tests.

Ultimately you will want to automatize the process, a simple solution is to use unittest so you can create test suites and just add the module names you want to test:

import unittest
import doctest

modules = ("model.car", 
           "tools.tool")

suite = unittest.TestSuite()
for mod in modules:
    suite.addTest(doctest.DocTestSuite(mod))
runner = unittest.TextTestRunner()
runner.run(suite)

Another solution (recommended) is to use a tool such as nose that automates this for you.

easy_install nose
nosetests --with-doctest # done :-)

And by the way, avoid from x import *. This works for quick scripts, but when your program will grow, you really will need to explicitly name what you import. Either import x or from x import y

e-satis
Maybe this is the best *available* solution. The downside is that now I have to run all tests in a one suite, and there are some very sloooow network dependent tests. :( Anyway, thanks for the answer.*nose* and future (?) keyword package (PEP366 from Pierre links) sound also worth of more investigation.
sankari