views:

240

answers:

2
+2  Q: 

Python Packages??

Ok, I think whatever I'm doing wrong, it's probably blindingly obvious, but I can't figure it out. I've read and re-read the tutorial section on packages and the only thing I can figure is that this won't work because I'm executing it directly. Here's the directory setup:

eulerproject/
  __init__.py
  euler1.py
  euler2.py
  ...
  eulern.py
  tests/
    __init__.py
    testeulern.py

Here are the contents of testeuler12.py (the first test module I've written):

import unittest
from .. import euler12

class Euler12UnitTests(unittest.TestCase):


    def testtriangle(self):
        """
        Ensure that the triangle number generator returns the first 10
        triangle numbers.

        """
        self.seq = [1,3,6,10,15,21,28,36,45,55]
        self.generator = euler12.trianglegenerator()
        self.results = []
        while len(self.results) != 10:
            self.results.append(self.generator.next())
        self.assertEqual(self.seq, self.results)

    def testdivisors(self):
        """
        Ensure that the divisors function can properly factor the number 28.

        """
        self.number = 28
        self.answer = [1,2,4,7,14,28]
        self.assertEqual(self.answer, euler12.divisors(self.number))


if __name__ == '__main__':

    unittest.main()

Now, when I execute this from IDLE and from the command line while in the directory, I get the following error:

Traceback (most recent call last):
  File "C:\Documents and Settings\jbennet\My Documents\Python\eulerproject\tests\testeuler12.py", line 2, in <module>
    from .. import euler12
ValueError: Attempted relative import in non-package

I think the problem is that since I'm running it directly, I can't do relative imports (because __name__ changes, and my vague understanding of the packages description is that __name__ is part of how it tells what package it's in), but in that case what do you guys suggest for how to import the 'production' code stored 1 level up from the test code?

+2  A: 

I had the same problem. I now use nose to run my tests, and relative imports are correctly handled.

Yeah, this whole relative import thing is confusing.

Virgil Dupras
I'm accepting the other answer just because it lets me do what I've been trying to do with relative imports. However, I am also downloading nose to use as my testing harness since it looks far more powerful than anything I could kludge together on my own.
Jonathanb
+4  A: 

Generally you would have a directory, the name of which is your package name, somewhere on your PYTHONPATH. For example:

eulerproject/
    euler/
        __init__.py
        euler1.py
        ...
        tests/
            ...
    setup.py

Then, you can either install this systemwide, or make sure to set PYTHONPATH=/path/to/eulerproject/:$PYTHONPATH when invoking your script.

An absolute import like this will then work:

from euler import euler1

Edit:

According to the Python docs, "modules intended for use as the main module of a Python application should always use absolute imports." (Cite)

So a test harness like nose, mentioned by the other answer, works because it imports packages rather than running them from the command line.

If you want to do things by hand, your runnable script needs to be outside the package hierarchy, like this:

eulerproject/
    runtests.py
    euler/
        __init__.py
        euler1.py
        ...
        tests/
            __init__.py
           testeulern.py

Now, runtests.py can do from euler.tests.testeulern import TestCase and testeulern.py can do from .. import euler1

Jason S
Yeah, but then your project don't take advantage of relative imports anymore. The nice thing about relative imports is that it makes your package self-contained. It is not dependent on it's main folder name and it cannot mis-import a unit from, for example, an obsolete version of the package, buried somewhere in the PYTHONPATH.
Virgil Dupras
Ok, so how would I set PYTHONPATH before invoking the script? Like if I wanted to be able to run this from two different locations on the same computer? ps: I'm also learning how to handle cloning/merging with mercurial, that's why it's in two places.
Jonathanb
Added a note on how relative imports can work if the module is imported from a script located outside the package hierarchy. Otherwise use a test harness.Setting environment variables depends on what you're working in. If you're using bash, just do PYTHONPATH=foo python scriptname.py
Jason S
Excellent! This answer, after the edit, does exactly what I want it to do. I've changed the hierarchy and I am going to write the euler.py script tonight in the root directory which will accept --test to enable the unit tests. This lets me preserve my relative imports, use the package structure, and experiment with testing my builds automatically.
Jonathanb