Thursday, July 30, 2009

Writing custom Property classes

The App Engine datastore API comes with a wide range of Property classes you can use to represent properties on your datastore models. Occasionally, though, you'd like to do something that the designers didn't think of. Fortunately, it's really easy to extend the Datastore API with custom Property classes. In this blog post, we'll demonstrate how to write your own Property class to store Python's decimal data type.

The Property interface is documented here, but there are two essential methods our DecimalProperty must override: get_value_for_datastore, and make_value_from_datastore. We also need to declare one field, data_type, that specifies the data type our property class will contain. Let's start with a straightforward implementation:

class DecimalProperty(db.Property):
  data_type = decimal.Decimal

  def get_value_for_datastore(self, model_instance):
    return str(super(DecimalProperty, self).get_value_for_datastore(model_instance))

  def make_value_from_datastore(self, value):
    return decimal.Decimal(value)

Note that in the case of get_value_for_datastore, we used super to call the parent class implementation, which handles the details of actually storing and retrieving the data stored in the model. We then simply convert the value to a string for storage in the datastore. In make_value_from_datastore, we reconstruct a Decimal object from the stored string.

That's all that's required for a basic property class! There is something missing from our example, though: We don't perform any validation to make sure that the user actually passed in a Decimal object. To do that, we override the validate method:

  def validate(self, value):
    value = super(DecimalProperty, self).validate(value)
    if value is None or isinstance(value, decimal.Decimal):
      return value
    elif isinstance(value, basestring):
      return decimal.Decimal(value)
    raise db.BadValueError("Property %s must be a Decimal or string." % self.name)

Again we start by calling the parent class's implementation, which performs some basic validity checks. Then we check if the value is a Decimal - in which case we return it - or a string, in which case we convert it to a decimal and return it. If the value is invalid, we raise a BadValueError.

Using our property class is as simple as using any other one:

class MyModel(db.Model):
  a_decimal = DecimalProperty()

model = MyModel()
model.a_decimal = decimal.Decimal("123.45")
model.put()

For a more in depth treatment of writing your own property class, see the article by Rafe Kaplan, Extending Model Properties.

1 comment:

newsteptechnology said...

Fantastic blog! I actually love how it's simple on my eyes as well as the data is well written. I am wondering how I may be notified whenever a new post has been made. I have subscribed to your rss feed which really should do the trick! Have a nice day!

Apartments For Sale Canary Wharf