开发者

In Django, why do I need to add the site name to imports in tests.py?

I've put some unit tests in mysite/vncbrowser/tests.py, and I can run these with:

cd mysite
python manage.py test vncbrowser

In tests.py, I import the model classes with:

from vncbrowser.models import Project, Stack, Integer3D, Double3D

... and test the insertion of an Integer3D into a custom field type with a test like:

class InsertionTest(TestCase):

    def test_stack_insertion(self):
        s = Stack()
        s.title = "Example Stack"
        s.image_base = "http://foo/bar/"
        s.dimension = Integer3D(x=2048, y=1536, z=460)
        s.resolution = Double3D(x=5.0001, y = 5.0002, z=9.0003)
        s.save()
        self.assertEqual(s.id, 1)

However, when I run the tests with python manage.py test vncbrowser, I find that a check of isinstance(value, Integer3D) in the models.py source is failing. It seems that in the models.py file, the bare reference to Integer3D (defined earlier in that file) has the full name vncbrowser.models.Integer3D, while the object that is passed in from the test has the full name mysite.vncbrowser.models.Integer3D.

The relevant code from models.py with some debugging statements is:

class Integer3D(object):
    [... elided ...]

class Integer3DField(models.Field):
    def to_python(self, value):
        a = Integer3D()
        print >> sys.stderr, "value is %s, of type %s" % (value, type(value))
        print >> sys.stderr, "but a new Integer3D instance is", type(a)
        if isinstance(value, Integer3D):
            print >> sys.stderr, "isinstance check worked"
            return value
        print >> sys.stderr, "isinstance check failed"

... that produces this output (with some newlines and spaces added for clarity):

value is <vncbrowser.models.Integer3D object at 0x22bbf90>, of type
      <class 'vncbrowser.models.Integer3D'>

but a new Integer3D instance is
      <class 'mysite.vncbrowser.models.Integer3D'>

isinstance check failed

I can make this test work by changing the import in tests.py to:

 from mysite.vncbrowser.models import Project, Stack, Integer3D, Double3D

... but I don't see why the mysite qualification should be required in the tests.py file. It doesn't seem to be required elsewhere in my django source. I'm sure I'm missing something obvious, but perhaps someone can explain?

(I'm not even sure why the from mysite.... imp开发者_Python百科ort works, in fact, since if I print sys.path from just before that statement, it includes the path /home/mark/foo/mysite/, but not /home/mark/foo/.)

My current working directory is /home/mark/foo/mysite/ when I run python manage.py test vncbrowser.


As requested, the layout of my the project is as follows:

 ── mysite
    ├── custom_postgresql_psycopg2
    │   ├── base.py
    │   └── __init__.py
    ├── __init__.py
    ├── manage.py
    ├── settings.py
    ├── urls.py
    └── vncbrowser
        ├── __init__.py
        ├── models.py
        ├── tables.sql
        ├── tests.py
        └── views.py

All of the __init__.py files listed above are empty. I'm using Python 2.6.5 and Django 1.3. I'm using Python in a virtualenv, and if I print "\n".join(sys.path) at the start of tests.py I get:

/home/mark/foo/mysite
/home/mark/foo/env/lib/python2.6/site-packages/distribute-0.6.10-py2.6.egg
/home/mark/foo/env/lib/python2.6
/home/mark/foo/env/lib/python2.6/plat-linux2
/home/mark/foo/env/lib/python2.6/lib-tk
/home/mark/foo/env/lib/python2.6/lib-old
/home/mark/foo/env/lib/python2.6/lib-dynload
/usr/lib/python2.6
/usr/lib64/python2.6
/usr/lib/python2.6/plat-linux2
/usr/lib/python2.6/lib-tk
/usr/lib64/python2.6/lib-tk
/home/mark/foo/env/lib/python2.6/site-packages

Update: as suggested in lbp's answer, I tried adding the following at the top of tests.py:

import vncbrowser as vnc_one
import mysite.vncbrowser as vnc_two

print "vnc_one:", vnc_one.__file__
print "vnc_two:", vnc_two.__file__

... which produced the output:

vnc_one: /home/mark/foo/mysite/vncbrowser/__init__.pyc
vnc_two: /home/mark/foo/mysite/../mysite/vncbrowser/__init__.pyc


You don't have to know all of your PYTHONPATH in order to know the one thing you actually want to know: where that other vncbrowser is coming from. Instead of printing out the python path, you can do the following:

import vncbrowser as vnc_one
import mysite.vncbrowser as vnc_two

print vnc_one.__file__
print vnc_two.__file__

And there you will see two different paths on your file system. Then you can start figuring out why.

This is just a wild guess, but I think vnc_one is installed somewhere in your python path and vnc_two resides in your source code. (edit: wrong guess)

Further, a random remark:

Also, you can make the import statement in tests.py simpler, by using

from models import ...

instead of

from XXX.models import ...


lbp's answer and comments helped me to understand and solve the problem, so that's the answer I'm accepting, but I thought it might be worth explaining in another answer where the sources of my confusion came from, since this may help other people.

When you run manage.py, it adds the site directory (/home/mark/foo/mysite, in my example) to sys.path before running the tests. So, if you then import a model from tests.py using the import line suggested in the Django documentation (which would be from vncbrowser.models import Integer3D in my case) and subsequently create an instance of an Integer3D, it will be of type vncbrowser.models.Integer3D, since the root of the package hierarchy is assumed to start at each entry in sys.path.

This name for the package is actually incorrect, however, since the application directory is nested in the site directory (as suggested by django-admin.py and the Django tutorial) and both of those are Python packages. That means that the real name for the class, and the one that is valid in models.py, is the fully qualified mysite.vncbrowser.models.Integer3D.

Normally, this doesn't create a problem, since, if you're writing Python well, uses of isinstance should be rare, and duck typing means that any difference in the full name of the class of particular objects should be irrelevant. However, when writing custom field types, you have to distinguish whether the to_python method has been called with a string or the object, and the documentation suggests using isinstance for that.

To avoid this confusion in the future, as lbp suggested in a comment, I'm now putting my applications in a separate (non-package) directory to avoid the risk of accidentally making the application dependent on the name of the site — this can easily happen with the strange default suggested hierarchy that nests application packages in the site package.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜