GAE inheritance filtering

Model inheritance and filtering

I talked about using GAE from the command line. I am going to reuse part of that to show how to make inheritance filtering. First, let’s separate the init code into gaeinit.py:

from appengine_django import LoadSdk
LoadSdk()

from google.appengine.tools import dev_appserver_main as dae
da = dae.DEFAULT_ARGS
da[dae.ARG_DATASTORE_PATH] = '/path/to/datastore/file'

from appengine_django import InstallAppengineHelperForDjango
InstallAppengineHelperForDjango()

Replace the datastore file path as appropriate – any path would do (e.g. ‘/tmp/model-inheritance.datastore’).

Let’s make file called model-inheritance.py in the same folder, initialize using gaeinit and put the models:

import gaeinit
from google.appengine.ext import db

# Main expando
class Word(db.Expando):
  ttype = None

  @classmethod
  def ffil(self, res):
    return res

  @classmethod
  def tfil(self, res):
    if self.ttype == None: 
      return res
    return res.filter('t =', self.ttype)

  @classmethod
  def all(self):
    res = super(Word, Word).all()
    res = self.tfil(res)
    res = self.ffil(res)
    return res

  def __str__(self):
    return "[%s, %s]" % (self.t, self.i)

# Sub-classes
class Adjective(Word):
  ttype = 'adjective'

class Noun(Word):
  ttype = 'noun'

class Verb(Word):
  ttype = 'verb'

class SecondVerb(Word):
  ttype = 'verb'
  
  @classmethod
  def ffil(self, res):
    return res.filter('i =', 2)

The base model class is called Word. It is a normal Expando, with the filtering additions and a standard str just for pretty printing (see Python docs for this one). Filtering additions are simple:

  • all replaces the default Expando all classmethod to allow additional filtering by tfil and ffil,
  • tfil is a type filtering method – it filters by ‘t =’, where t is the attribute on the Expando,
  • ffil is a function filtering method – by default, doesn’t do anything, so it’s basically an IoC placeholder.

The subclasses just define three word types – adjective, noun and verb. SecondVerb is a contrived example that shows how to use ffil to additionally filter by another property, in this case the dummy i variable.

Here’s how we can test and use it:

# Populate with test data
db.delete(Word.all())

for t in ['adjective', 'noun', 'verb']:
  for i in range(0, 4):
    d = Word(t=t, i=i)
    d.put()

# List
for k in [Word, Adjective, Noun, Verb, SecondVerb]:
  print "---- %s" % k.__name__
  for r in k.all():
    print "%s" % r

The above prints the following:

---- Word
[adjective, 0]
[adjective, 1]
[adjective, 2]
[adjective, 3]
[noun, 0]
[noun, 1]
[noun, 2]
[noun, 3]
[verb, 0]
[verb, 1]
[verb, 2]
[verb, 3]
---- Adjective
[adjective, 0]
[adjective, 1]
[adjective, 2]
[adjective, 3]
---- Noun
[noun, 0]
[noun, 1]
[noun, 2]
[noun, 3]
---- Verb
[verb, 0]
[verb, 1]
[verb, 2]
[verb, 3]
---- SecondVerb
[verb, 2]

Obviously, this is query-only – you need to use the base class to input all the values, as using e.g. Noun.put() would put it under Noun, not under Word. I find it useful for model categorization, I find this not to be a huge drawback in these cases.