views:

153

answers:

2

I'm looking to implement a property class for appengine, very similar to the existing db.ReferenceProperty. I am implementing my own version because I want some other default return values. My question is, how do I make the property remember its returned value, so that the datastore query is only performed the first time the property is fetched? What I had is below, and it does not work. I read that the Property classes do not belong to the instances, but to the model definition, so I guess that the return value is not cached for each instance, but overwritten on the model every time. Where should I store this _resolved variable?

class PageProperty(db.Property):
  data_type = Page

  def get_value_for_datastore(self, model_instance):
    page = super(PageProperty, self).get_value_for_datastore(model_instance)        
    self._resolved = page
    return page.key().name()

  def make_value_from_datastore(self, value):
    if not hasattr(self, '_resolved'):
        self._resolved = Page.get_by_name(value)
    return self._resolved

Edit

Alex' answer is certainly usable. But it seems that the built-in db.ReferenceProperty does store the _RESOLVED variable on the model instance. As evidenced by:

[...]
    setattr(model_instance, self.__resolved_attr_name(), value)
[...]

def __resolved_attr_name(self):
    return '_RESOLVED' + self._attr_name()

The get_value_for_datastore method is passed the model instance, but make_value_from_datastore is not, so how do they find the _RESOLVED property from that method?

Edit 2

From the code I gather that google is using the __get__() and __set__() methods, both of which do get the model instance as an argument. Are those usable in custom classes? What is the difference with get_value_for_datastore and its counterpart?

+2  A: 

A PageProperty instance exists per-model, not per-entity (where an entity is an instance of the model class). So I think you need a dictionary that maps pagename -> Page entity, instead of a single attribute per PageProperty instance. E.g., maybe something like...:

class PageProperty(db.Property):
  data_type = Page

  def __init__(self, *a, **k):
    super(PageProperty, self).__init__(*a, **k)
    self._mycache = {}       

  def get_value_for_datastore(self, model_instance):
    page = super(PageProperty, self).get_value_for_datastore(model_instance)        
    name = page.key().name()
    self._mycache[name] = page
    return name

  def make_value_from_datastore(self, value):
    if value not in self._mycache:
        self._mycache[value] = Page.get_by_name(value)
    return self._mycache[value]
Alex Martelli
I thought I had overlooked a way to access the entity from the `Property`.
Noio
No, the Property instance belongs to the whole model (that's why you set it as part of the class body!), not to each specific entity (instance of the model), that's the key point -- so there can't be a way to access a specific entity given only the instance of Property.
Alex Martelli
You don't need a dictionary - simply store the property values on the entity in question. This is the approach that the ReferenceProperty class takes (along with, in fact, every other property class). Keeping a dict will result in one of ever increasing size!
Nick Johnson
+1  A: 

If you only want to change some small part of the behaviour of ReferenceProperty, you may want to simply extend it, overriding its default_value method. You may find the source for ReferenceProperty to be instructive.

Nick Johnson
I want it to return a new entity of its kind when the stored key is not found. That, and some other things. Regarding the source. I don't understand how the `ReferenceProperty` is storing the `_RESOLVED` entity _on the model instance_, and not, like Alex explained, in a dict on the property instance.
Noio
It uses getattr/setattr on the passed in model instance to set/retrieve the _resolved property. You should be able to implement the behaviour you want by subclassing ReferenceProperty, overriding __get__, and having it catch db.Error. If an error is raised, create a new instance, call __set__, and return the instance.
Nick Johnson