Wednesday, June 19, 2013

How To Organize a GAE Project

For better or worst, google app engine allows us to use whatever project tree structure we want, as long as we connect all urls with appropriate handlers in app.yaml it will all work just fine.
My first gae app had models.py for all my models (Django school, can't help it :) ) and a separate {view_name}.py for each url. I know, it's ugly, but following gae tutorial lead me to this structure and it all worked ok. First sign of having the wrong kind of project structure was when I've written a custom module to enhance ndb and couldn't make dev_appserver recognize it.
After long hours of digging through Internet and a fair amount of frustration, I finally turned to my mentor for an answer and he pointed me to a certain StackOverflow question regarding same problem.
And here is my final project structure:
project/
   src/
      app1/
         lib/
            __init__.py
            mycustommodule.py
         static/
            img/
            css/
            js/
         templates/
         tests/
            __init__.py
            mytests.py
         __init__.py
         app.yaml
         cron.yaml
         lib_path.py
         index.yaml
         models.py
         urls.py
         views.py
Most of what you see is self-explanetory, I think.
index.yaml is produced by gae when you upload your application to the cloud, do not touch it.
lib_path.py is a simple file adding lib/ to sys.path in such a way that dev_appserver recognizes it:
import os
import sys

sys.path.append(os.path.join(os.path.dirname(__file__), 'lib'))
Before this, I had linked my custom module in site-packages, which allowed me to import my modul in the consol, test and use it just fine, but when I ran dev_appserver it crashed on import error. I went as far as creating a setup.py and doing this didn't help much, and it also didn't feel right.
Now, to use my custom module, I can import it like this anywhere in my application:
from lib import mycustommodule
and woot! it works. How nice and simple indeed.
Lets take a look at urls.py:
import lib_path

import wsgiref.handlers
import webapp2
import views

url_map = [
    ('/', views.MainPage),
]

app = webapp2.WSGIApplication(url_map, debug=True)
Here is where I call for a lib_path and it has to be called first. I don't call it anywhere else.
My urls.py:
application: helloworld
version: 1
runtime: python27
api_version: 1
threadsafe: yes

handlers:
- url: /static
  static_dir: static

- url: /.*
  script: urls.app

libraries:
- name: webapp2
  version: "2.5.1"
- name: jinja2
  version: "2.6"
It is better to use a real version number than 'latest'. This way if one of the functions you were using is not present in the newest version it won't break your code and keep your applicaiton running.
And let's not forget views.py :
import os

import jinja2
import webapp2

from models import *

jinja_environment = jinja2.Environment(
    loader=jinja2.FileSystemLoader(os.path.dirname(__file__)))

class MainPage(webapp2.RedirectHandler):

    def get(self):

        msg = 'Hello World!'

        template_values = {
            'data': msg,
        }
        template = jinja_environment.get_template('/templates/home.html')
        self.response.out.write(template.render(template_values))
Simple, right? ;)
One other thing, I suggest to write all the queries (with filters) in your models.py under @classmethod for following reasons:
  1. Simple to use -  if anyone else works with you on this project, he doesn't have to know how to query, so one less place for errors.
  2. Easy to change - if google will change how BigTable does this query or that, you will have to update your code in one place only.
  3. Easy to maintain.
That's it. Hope it helps you to get started with developing your application and not spending precious time on orginising imports.

No comments:

Post a Comment