## Decimal property for Google AppEngine NDB

### from Branko's Thought Dump

###### Feb 7, 2014, 8:22:00 PM

While GAE offers a pretty good selection of default properties to go with your models, there are pretty amazing things you can do with custom properties. In this post, we'll take a look at how to implement a custom property for storing Python's `Decimal` type.

When creating a new custom property, you first have to decide on the base property you will subclass. When deciding what base to use, consider things like whether you need the property to be indexed, or whether you want to do comparison lookups (e.g., `>=`). For this example, we will use a `ndb.IntegerProperty` as base, since we want to be able to compare values in our queries, not just store and retrieve them.

``from google.appengine.ext import ndbclass DecimalProperty(ndb.IntegerProperty):    pass``

There are three things you need to do with your values. Validation goes without saying. You also need to decide how you will convert the value to one that the base property understands (`int` in this case), and how to convert from the base value to our custom value (`Decimal` in our case). Let's take a look at each step.

#### Custom validation

As I mentioned earlier, this is going to be very simple validation. If the `Decimal` class raises an exception, validation will fail. You can use a proper validator that checks the incoming value and raises an appropriate exception (e.g., `BadValueError`), but I'll leave that as an exercise for you.

``from decimal import Decimalclass DecimalProperty(ndb.IntegerProperty):        def _validate(self, value):        return Decimal(value)``

#### Conversion to base value

To convert a value to its base type, you need to implement a `_to_base_type` method. Our implementation will convert the value to an integer using the `int()` function.

``class DecimalProperty(ndb.IntegerProperty):        def _validate(self, value):        return Decimal(value)    def _to_base_type(self, value):        return int(value)``

Note that `value` in this context is the `Decimal` value.

#### Conversion from base value

The final step is to define `_from_base_type` method, which is called when the data is retrieved from the datastore. We will just convert it to `Decimal`.

``class DecimalProperty(ndb.IntegerProperty):        def _validate(self, value):        return Decimal(value)    def _to_base_type(self, value):        return int(value)    def _from_base_type(self, value):        return Decimal(value)``

If you've been paying attention so far, you'd notice that this implementation suffers from one big issue. Since we're converting everything to integer, the values are rounded in a very bad way, and the property won't take into account any floating points. To correct this, we will first modify the property to accept `precision` argument with a sane default (say, 2 digits after floating point).

``class DecimalProperty(ndb.IntegerProperty):    float_prec = 2    def __init__(self, float_prec=None, **kwargs):        if float_prec is not None:            self.float_prec = float_prec        super(DecimalProperty, self).__init__(**kwargs)        def _validate(self, value):        return Decimal(value)    def _to_base_type(self, value):        return int(value)    def _from_base_type(self, value):        return Decimal(value)``

Now with this parameter, we can adjust the precision of the actual values stored in the datastore:

``class DecimalProperty(ndb.IntegerProperty):    float_prec = 2    def __init__(self, float_prec=None, **kwargs):        if float_prec is not None:            self.float_prec = float_prec        super(DecimalProperty, self).__init__(**kwargs)        def _validate(self, value):        return Decimal(value)    def _to_base_type(self, value):        return int(round(value * (10 ** self.float_prec)))    def _from_base_type(self, value):        return Decimal(value) / (10 ** self.float_prec)``

#### Conclusion

With this we have a complete working implementation (save for any typos that I might have made) that will store and retrieve our `Decimal` values. Using `ndb.IntegerProperty` as the base class also has other benefits. The way NDB works, we get all the comparison operators for free, and they work with our scaled precision without a problem. With a model like this:

``class MyModel(ndb.Model):    dec = DecimalProperty()``

you can do the usual:

``MyModel.query(MyModel.dec>='12.4')MyModel.query(MyModel.dec=='0.4')``

The only caveat is that you need to specify a bigger precision if you expect lots of digits after the floating point since, unlike the `Decimal` type, this implementation does not support arbitrary precision. If comparison operators aren't needed, but you do need arbitrary precision, you can base the implementation on `ndb.StringProperty` instead. The `==` operator should work fine even in that case, and you won't have to worry about precision.