views:

196

answers:

2

I want to make attributes of GAE Model properties. The reason is for cases like to turn the value into uppercase before storing it. For a plain Python class, I would do something like:

Foo(db.Model):
    def get_attr(self):
       return self.something
    def set_attr(self, value):
       self.something = value.upper() if value != None else None
    attr = property(get_attr, set_attr)

However, GAE Datastore have their own concept of Property class, I looked into the documentation and it seems that I could override get_value_for_datastore(model_instance) to achieve my goal. Nevertheless, I don't know what model_instance is and how to extract the corresponding field from it.

Is overriding GAE Property classes the right way to provides getter/setter-like functionality? If so, how to do it?

Added:

One potential issue of overriding get_value_for_datastore that I think of is it might not get called before the object was put into datastore. Hence getting the attribute before storing the object would yield an incorrect value.

+3  A: 

Subclassing GAE's Property class is especially helpful if you want more than one "field" with similar behavior, in one or more models. Don't worry, get_value_for_datastore and make_value_from_datastore are going to get called, on any store and fetch respectively -- so if you need to do anything fancy (including but not limited to uppercasing a string, which isn't actually all that fancy;-), overriding these methods in your subclass is just fine.

Edit: let's see some example code (net of imports and main):

class MyStringProperty(db.StringProperty):
    def get_value_for_datastore(self, model_instance):
        vv = db.StringProperty.get_value_for_datastore(self, model_instance)
        return vv.upper()

class MyModel(db.Model):
    foo = MyStringProperty()

class MainHandler(webapp.RequestHandler):

    def get(self):
        my = MyModel(foo='Hello World')
        k = my.put()
        mm = MyModel.get(k)
        s = mm.foo
        self.response.out.write('The secret word is: %r' % s)

This shows you the string's been uppercased in the datastore -- but if you change the get call to a simple mm = my you'll see the in-memory instance wasn't affected.

But, a db.Property instance itself is a descriptor -- wrapping it into a built-in property (a completely different descriptor) will not work well with the datastore (for example, you can't write GQL queries based on field names that aren't really instances of db.Property but instances of property -- those fields are not in the datastore!).

So if you want to work with both the datastore and for instances of Model that have never actually been to the datastore and back, you'll have to choose two names for what's logically "the same" field -- one is the name of the attribute you'll use on in-memory model instances, and that one can be a built-in property; the other one is the name of the attribute that ends up in the datastore, and that one needs to be an instance of a db.Property subclass and it's this second name that you'll need to use in queries. Of course the methods underlying the first name need to read and write the second name, but you can't just "hide" the latter because that's the name that's going to be in the datastore, and so that's the name that will make sense to queries!

Alex Martelli
I tried extending StringProperty with get_value_for_datastore overridden but I didn't seem to do anything. Why is that?
ejel
Ignore my above comment, that was my bad. Override StringProperty does work. However, as I was afraid, it was called only after the object was put to the datastore. I would like to have an approach which always in control like a normal setter.
ejel
"Overriding StringProperty" is a pretty weird phrase -- I was talking about subclassing `Property` (not any existing concrete subclass thereof) and overriding `get_value_from_datastore` and `make_value_from_datastore` (which are **methods**, _not_ **classes** -- "overriding" a class is essentially nonsense). Can you show us a code example of what you're trying to communicate via this weird "overriding StringProperty" phrase and what observed behavior leads you to believe it isn't working as documented?
Alex Martelli
Sorry for the confusing. What I mean was actually subclassing StringProperty since the only method I need to override is get_value_for_datastore and I don't need to re-implement others and that does work. The thing that didn't work is I need to put it and get it back from datastore before the property method get called, as you also explained.
ejel
@ejel, so go with the two-names approach
Alex Martelli
You don't need two properties - a derived one will serve for both purposes, regardless of if it's been stored to the datastore.
Nick Johnson
+1  A: 

What you want is a DerivedProperty. The procedure for writing one is outlined in that post - it's similar to what Alex describes, but by overriding get instead of get_value_for_datastore, you avoid issues with needing to write to the datastore to update it. My aetycoon library has it and other useful properties included.

Nick Johnson
Your suggestion seems like a good solution. However, going this way, I am still required to have two properties, the original one and the derived, lower-case one, is that right? I am still kind of hesitating to have two properties only because I need the attribute to be lowercase (and I don't care about the original string).
ejel
That's correct. You don't need two property names just to access the lower-cased version, though. If you don't care about the original string, you can just lower-case it on input, and store it that way, though.
Nick Johnson
Yes, I don't care about the original string. However, even though I can do the lower-casing on the input, I prefer to enforce it at the data layer as well to control data consistency.
ejel