Automatic key generation with App Engine
After playing Google App Engine for a few days, I’ve realised the power that even the basic framework provided by Google, webapp, provides. Working with Python adds a level of flexibility that I never had when working with PHP many years ago (although this was before OO had really infected PHP).
While working on my first real application for app engine I soon came across the problem of creating sensible key names for objects. If you aren’t familiar with the data object models in app engine, each object has a unique key, generated automatically when the object is created, and a key name (property: key_name) which can be specified by the user. The key name can be very useful if you need object identifiers which are human-readable, such as when you use them as part of your apps URLs.
All data objects are defined as Python classes which subclass the db.Model class, provided by Google. A simple example, from the app I’m working on, is an object which holds information about an author:
class Author(db.Model): first_name = db.StringProperty(required=True) middle_name = db.StringProperty() last_name = db.StringProperty(required=True)
Creating a new author is a simple matter of calling the class and passing property values either as keyword arguments or by accessing the property through the new instance:
a = Author(first_name='John',
last_name='Smith')
a.middle_name = 'Thomas'
We can specify the optional key_name property when we create the object:
a = Author(first_name='Alan',
middle_name='Simon',
last_name='Jones',
key_name='alansimonjones')
However, it’s more than likely that if we want to use the key_name property for a class of data objects, we will generate it procedurally based on the initial property values of the object. Above, we create the key name using the authors names. You could create a function which wraps the creation of the author object which does this for you:
def newAuthor(first_name, last_name, middle_name = ''):
parts = [first_name, middle_name, last_name]
# Define the key name from the authors names.
# We split() and join each name to remove any spaces
key_name = ''.join([''.join(x.split()) for x in parts]).lower()
return Author(first_name=first_name,
last_name=last_name,
middle_name=middle_name,
key_name=key_name)
but this is quite a lot of boilerplate code to solve a simple problem. Moreover, you need to make sure everyone working on the code is aware that this function must be used to create new author objects, rather than by calling the object class directly.
A more elegant solution is to encapsulate the creation of the key_name property in the class definition itself. This is bread and butter stuff for any Pythonista, and is handled by defining a constructor for our author object, using it to define our key name and then to manually call the constructor of the db.Model class. The last step is necessary as once we define our own constructor, the constructor of the super class is not called automatically. To add to the things we need to handle, it’s important to know that the db.Model class uses dynamic positional and keyword arguments. If that makes no sense to you, don’t worry, it’s simpler than you may think.
Python provides an additional way of specifying standard function arguments using the *variable syntax, and keyword arguments using the **variable syntax. If used in the definition of a function, the *variable will be a tuple of positional arguments, and **variable will be a dictionary relating keywords to variables. Therefore, we must make sure our new constructor has the same parameters as db.Model to ensure 100% compatibility.
Here is the updated author class, with the new __init__() function defined. It has been slightly altered because we access the keyword values using the kw dictionary and we need to check whether each of the properties have been specified or not by examining kw.keys():
class Author(db.Model):
first_name = db.StringProperty(required=True)
middle_name = db.StringProperty()
last_name = db.StringProperty(required=True)
def __init__(self, *args, **kw):
parts = ['first_name', 'middle_name', 'last_name']
# Define the key name from the authors names.
# We split() and join each name to remove any spaces
key_name = ''.join([''.join(kw[x].split()) for x in parts if x in kw.keys()]).lower()
# Insert the new key_name property into the keyword arguments
kw['key_name'] = key_name
# Call the constructor of the base class
db.Model.__init__(self, *args, **kw)
We can now create new author objects and the key names will be generated automatically and will follow our specified format:
a = Author(first_name='John', last_name='Smith') # Will print 'johnsmith' print a.key().name() a = Author(first_name='John', middle_name='Alan Mike', last_name='Smith') # Will print 'johnalanmikesmith' print a.key().name()
Being able to alter the properties of an object or preconfigure core properties at the time of instantiation can be useful in all sorts of situations, but this particular example is one which I can see myself using a lot.
Thank you for breaking it down. Really helpful post.
Thanks for the post. Solved a similar problem I was having when battling with some GAE datastore vs Django ORM problems.