Leon Overweel
Leon Overweel

Reputation: 1567

Import across modules in Google App Engine

I have a Python Google App Engine project structured as follows:

app/
    handlers/
        register_user.py
    models/
        user.py

The user.py file contains a class User(ndb.Model).

I'm trying to access the User class from register_user.py to create and put a new user in the database. Normally, I'd just import it like this:

from ..models.user import User

But this errors out because I'm trying to import something from above my root package - so I'm guessing models is my root package, and I can't get back to the app package?

Right now, I'm able to work around it by importing like this:

import importlib
User = importlib.import_module('models.user').User

I think this is kind of messy, though. So what's the "right" way of importing my User class?

Edit: The full stack trace:

Attempted relative import beyond toplevel package (/base/data/home/runtimes/python27/python27_lib/versions/third_party/webapp2-2.5.2/webapp2.py:1552)
Traceback (most recent call last):
  File "/base/data/home/runtimes/python27/python27_lib/versions/third_party/webapp2-2.5.2/webapp2.py", line 1535, in __call__
    rv = self.handle_exception(request, response, e)
  File "/base/data/home/runtimes/python27/python27_lib/versions/third_party/webapp2-2.5.2/webapp2.py", line 1529, in __call__
    rv = self.router.dispatch(request, response)
  File "/base/data/home/runtimes/python27/python27_lib/versions/third_party/webapp2-2.5.2/webapp2.py", line 1278, in default_dispatcher
    return route.handler_adapter(request, response)
  File "/base/data/home/runtimes/python27/python27_lib/versions/third_party/webapp2-2.5.2/webapp2.py", line 1102, in __call__
    return handler.dispatch()
  File "/base/data/home/runtimes/python27/python27_lib/versions/third_party/webapp2-2.5.2/webapp2.py", line 572, in dispatch
    return self.handle_exception(e, self.app.debug)
  File "/base/data/home/runtimes/python27/python27_lib/versions/third_party/webapp2-2.5.2/webapp2.py", line 570, in dispatch
    return method(*args, **kwargs)
  File "/base/data/home/apps/s~polly-chat/1.394430414829237783/main.py", line 48, in post
    receive_message(messaging_event)
  File "/base/data/home/apps/s~polly-chat/1.394430414829237783/messaging/handler.py", line 39, in receive_message
    intent_picker.respond_to_postback(messaging_event)
  File "/base/data/home/apps/s~polly-chat/1.394430414829237783/messaging/intent_picker.py", line 71, in respond_to_postback
    intent = importlib.import_module('intents.register_user')
  File "/base/data/home/runtimes/python27/python27_dist/lib/python2.7/importlib/__init__.py", line 37, in import_module
    __import__(name)
  File "/base/data/home/apps/s~polly-chat/1.394430414829237783/intents/register_user.py", line 1, in <module>
    from ..models import messenger_user
ValueError: Attempted relative import beyond toplevel package

(The package names here are different; I simplified them above to make the example more general)

Upvotes: 0

Views: 331

Answers (2)

Bill Prin
Bill Prin

Reputation: 2518

I think Dan is on the right track, however there is no need to vendor your own code. The vendoring system is to manage third-party dependencies with pip and should be totally unnecessary for your use case, and vendoring your own code would violate the conventions.

Based on what you've told us, you should be able to import your code with just

from models import user

If that doesn't work, you should figure out why, but you definitely do not need the vendor extension or importlib to solve it.

Your base module is wherever your base WSGI application is located, which is going to be defined by where your app.yaml routes to. Typically, your app.yaml will contain something like:

- url: .*  # This regex directs all routes to main.app
  script: main.app

In this case, in the same directory as app.yaml there is a main.py that contains an app WSGI application. In some other cases, script might be application.main.app, in which case the app variable is in application/main.py and then the application directory would be the base directory.

Every Python package containing modules should contain an __init__.py file in its directory, as Dan mentioned. As a side note if you do use a lib directory for third-party code it won't contain an __init__.py since it's not a Python package (just a directory that contains Python packages). The fact that its not a package is why you use the vendor extension Dan described to make sure the packages it contains are on the import path.

In my experience, relative imports are rarely needed and can get you into problems like these so I would just avoid them.

If you're still stuck, lay out the entire file structure of your application, including the app.yaml contents, and each of the subdirectories, including whether they contain an __init__.py or not.

Upvotes: 1

Dan Cornilescu
Dan Cornilescu

Reputation: 39814

The way I approached this was using the GAE 3rd party lib vendoring technique:

  • created appengine_config.py:

content:

from google.appengine.ext import vendor

# Add any libraries installed in the "lib" folder.
vendor.add('lib')
  • created the /app/lib dir
  • added an empty __init__.py file in the models dir to make it a package
  • placed/moved/symlinked the models dir inside the /app/lib dir

With this the models can be referenced using:

from models.user import User

Possibly of interest:

Upvotes: 0

Related Questions