Skip to content

Having issues with imports?

hangtwenty edited this page Mar 2, 2013 · 1 revision

TL;DR: Don't name features/steps in any way that conflicts with other modules.

Instead, always prefix your step files with step or steps. If you have registration.feature, name the stepfile steps_registration.py (etc.).


Here's something I just banged my head against for a while.

I am writing a Django project test-first. I was about to add features related to user registration and so on. So I created features/registration.feature and features/steps/registration.py.

Then I wrote code for it. The code used django-registration. Everything seemed in order, so I ran behave on the feature again. That produced the following error:

      ...
        File "/home/h/workspace/showroom/djangoproject/apps/users/urls.py", line 9, in <module>
          from registration import forms
      ImportError: cannot import name forms

Huh. I look at the relevant snippet in my users.urls module:

from registration.forms import (
    RegistrationFormNoUsername
)

Nothing too strange here. What if I just do import registration? I try it: it works. Now I decide to do some manual testing. Confusingly, there are no issues. No import issues. The module works fine and I am seeing what I expect (and exactly what registration.feature and steps/registration.py specify).

So it must be import funkiness. Submodules of registration won't import. So I tried using pkgutil to list the submodules (based on this snippet), something like this:

def inspect_submodules_of_imported_package():
    import pkgutil

    # import the package to inspect, i.e. registration (django-registration)
    import registration

    package = registration
    msgs = []
    for importer, modname, ispkg in pkgutil.iter_modules(package.__path__):
        msgs.append("Found submodule %s (is a package: %s)" % (modname, ispkg))
    raise Exception(msgs)  # stop process, print informnation about sumbodules
inspect_submodules_of_imported_package()

But this produced an error too:

File "/home/h/workspace/showroom/djangoproject/apps/users/urls.py", line 15, in <module>
          for importer, modname, ispkg in pkgutil.iter_modules(package.__path__):
      AttributeError: 'module' object has no attribute '__path__'

If the thing you're importing is a package, it should have a __path__ attribute. However if it is a single module and not a package, it will not have a __path__ attribute (see here). So registration is not a package (does not have submodules)? I triple-checked that I installed it correctly, so what's going on here?

The answer lied in the module's __file__ attribute. I replaced the snippet above with this one:

def locate_imported_module():
    import registration
    raise Exception(registration.__file__)
locate_imported_module()

This produced the following output:

File "/home/h/workspace/showroom/djangoproject/apps/users/urls.py", line 13, in <module>
          raise Exception(registration.__file__)
      Exception: /home/h/workspace/showroom/djangoproject/features/steps/registration.pyc

Aha! The problem was that I had named my feature (and step) something that conflicted with the registration module. As a result users.urls was actually trying to import features.steps.registration instead of the registration we wanted (django-registration). What threw me for a loop was that this was happening only when running behave tests, because outside of behave's case, features/steps/registration.py is not on my path (and thus, doesn't conflict).

Prefix your step files with step or steps

Don't name a step module the same thing as another module, ever!

There's an easy way to avoid naming conflicts all the time: just add a prefix or suffix to all of the step files' names.

Instead of naming this file steps/registration.py, name it steps/steps_registration.py. At the time of writing, the behave tutorial does not make this clear. (But jenisys's excellent tutorial does).