Wednesday, November 7, 2012

AppEngine: Migrating from Python 2.5 to Python 2.7 on Google App Engine in simple steps

I tried moving an old app on Google App Engine from Python 2.5 to Python 2.7. Google Developers has provided a nice writeup, with all the prerequisites. However, being a noob, I found it rather difficult.

The first problem being, main.app. In Python 2.5, the script is named after the filename. If you have a file called main.py which you want to be called to handle certain urls, you put the script name as main.py, under handlers in app.yaml.

In the Python 2.7 runtime, app engine wants you to use the dot notation to call the appropriate "WSGI application object". So app.yaml has main.app instaed of main.py.

handlers:
- url: /.*
  script: main.app

And by main.app, you are referring to the app attribute of main.py file(i.e the module called "main")

I did not understand this at first, and changed the line in app.yaml to main.app and changed the name of the file main.py to main.app

ImportError: No module named main 

On running the application dev_appserver, I got the following error

ERROR    wsgi.py:219] 
Traceback (most recent call last):
  File "wsgi.py", line 196, in Handle
    handler = _config_handle.add_wsgi_middleware(self._LoadHandler())
  File "wsgi.py", line 255, in _LoadHandler
    handler = __import__(path[0])

ImportError: No module named main
 

So, I renamed main.app(the incorrectly named file) back to main.py.

ImportError: module 'main' has no attribute app

I changed the rest of the app as per the instructions on the Migrating to Python 2.7 tutorial. And I ran the app using dev_appserver.py. This time, I got a different error.

ImportError:  has no attribute app

If you see the old application code below,

application = webapp.WSGIApplication(
                                     [('/', MainPage)],
                                     debug=True)

The application object is called application instead of app. So, you could either rename application in main.py to app or refer to main.application in your app.yaml file. The old and new files are given below.

Structure of the application.

appname-root-folder
 -main.py
 -app.yaml
 -...

Old application

app.yaml

application: appname
version: 1
runtime: python
api_version: 1

handlers:
- url: /.*
  script: main.py

main.py

from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app

class MainPage(webapp.RequestHandler):
    def get(self):
        self.response.headers['Content-Type'] = 'text/plain'
        self.response.out.write('Hello, webapp World!')

application = webapp.WSGIApplication(
                                     [('/', MainPage)],
                                     debug=True)

def main():
    run_wsgi_app(application)

if __name__ == "__main__":
    main() 

New application (using webapp)

Webapp is deprecated in Python 2.7 under Google App Engine. But, if you want to stick with webapp for some more time, you can do so.

app.yaml

application: appname
version: 1
runtime: python27
api_version: 1

threadsafe: true

handlers:
- url: /.*
  script: main.application

main.py

from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app

class MainPage(webapp.RequestHandler):
    def get(self):
        self.response.headers['Content-Type'] = 'text/plain'
        self.response.out.write('Hello, webapp World!')

application = webapp.WSGIApplication(
                                     [('/', MainPage)],
                                     debug=True)

New application (with webapp2)
 
app.yaml remains the same. In main.py, we do not have to import run_wsgi_app.

app.yaml

application: appname
version: 1
runtime: python27
api_version: 1

threadsafe: true

handlers:
- url: /.*
  script: main.application

main.py
import webapp2

class MainPage(webapp2.RequestHandler):
    def get(self):
        self.response.headers['Content-Type'] = 'text/plain'
        self.response.out.write('Hello, webapp World!')

application = webapp2.WSGIApplication(
                                     [('/', MainPage)],
                                     debug=True)

Note: In the new app.yaml, the script is referred to as

... 
handlers:
- url: /.*
  script: main.application
... 

Instead of main.application, you can call the script as main.app or main.whatever. Simply use the same name for the application object in your script file. i.e If you use main.app in app.yaml, change the line in main.py accordingly as follows.
...
app = webapp2.WSGIApplication(
                                     [('/', MainPage)],
                                     debug=True)

...


I don't have a main.py, where should I make the changes to migrate to Python 2.7?

Go to your app folder and open the app.yaml file. In this, you may already be specifying a file named main.py or whatever_name.py to handle certain urls. In Python 2.5, the text in app.yaml looked as follows

handlers:
- url: /.*
  script: main.py

If instead of main.py you have the following text
handlers:
- url: /.*
  script: helloworld.py

Then you should open the file named helloworld.py and edit it instead.
Do comment, if you spot errors in the code.

tl;dr
1. Open app.yaml, change as given in New application above
2. Open script file(main.py), change as given in New application above
3. Done!

Sources:
Code adapted from the following sources
1. Using the webapp Framework - Google App Engine
2. Migrating to Python 2.7 - Google App Engine

1 comment: