开发者

Elegant way to avoid .put() on unchanged entities

A reoccurring pattern in my Python programming on GAE is getting some entity from the data store, then possibly changing that entity based on various conditions. In the end I need to .put() the entity back to the data store to ensure that any changes that might have been made to it get saved.

However often there were no changes actually made and the final .put() is just a waste of money. How to easily make sure that I only put an entity if it has really changed?

The code might look something like

def handle_get_request():
    entity = Entity.get_by_key_开发者_JAVA技巧name("foobar")

    if phase_of_moon() == "full":
       entity.werewolf = True
    if random.choice([True, False]):
       entity.lucky = True
    if some_complicated_condition:
       entity.answer = 42

    entity.put()

I could maintain a "changed" flag which I set if any condition changed the entity, but that seems very brittle. If I forget to set it somewhere, then changes would be lost.

What I ended up using

def handle_get_request():
    entity = Entity.get_by_key_name("foobar")
    original_xml = entity.to_xml()

    if phase_of_moon() == "full":
       entity.werewolf = True
    if random.choice([True, False]):
       entity.lucky = True
    if some_complicated_condition:
       entity.answer = 42

    if entity.to_xml() != original_xml: entity.put()

I would not call this "elegant". Elegant would be if the object just saved itself automatically in the end, but I felt this was simple and readable enough to do for now.


Why not check if the result equals (==) the original and so decide whether to save it. This depends on a correctly implemented __eq__, but by default a field-by-field comparison based on the __dict__ should do it.

  def __eq__(self, other) : 
        return self.__dict__ == other.__dict__

(Be sure that the other rich comparison and hash operators work correctly if you do this. See here.)


One possible solution is using a wrapper that tracks any attribute change:

class Wrapper(object):
    def __init__(self, x):
        self._x = x
        self._changed = False

    def __setattr__(self, name, value):
        if name[:1] == "_":
            object.__setattr__(self, name, value)
        else:
            if getattr(self._x, name) != value:
                setattr(self._x, name, value)
                self._changed = True

    def __getattribute__(self, name):
        if name[:1] == "_":
            return object.__getattribute__(self, name)
        return getattr(self._x, name)

class Contact:
    def __init__(self, name, address):
        self.name = name
        self.address = address


c = Contact("Me", "Here")
w = Wrapper(c)

print w.name               # --> Me
w.name = w.name
print w.name, w._changed   # --> Me False
w.name = "6502"
print w.name, w._changed   # --> 6502 True


This answer is a part of an question i posted about a Python checksum of a dict With the answers of this question I developed a method to generate checksum from a db.Model.

This is an example:

>>> class Actor(db.Model):
...   name = db.StringProperty()
...   age = db.IntegerProperty()
... 
>>> u = Actor(name="John Doe", age=26)
>>> util.checksum_from_model(u, Actor)
'-42156217'
>>> u.age = 47
>>> checksum_from_model(u, Actor)
'-63393076'

I defined these methods:

def checksum_from_model(ref, model, exclude_keys=[], exclude_properties=[]):
    """Returns the checksum of a db.Model.

    Attributes:
      ref: The reference og the db.Model
      model: The model type instance of db.Model.

      exclude_keys: To exclude a list of properties name like 'updated'
      exclude_properties: To exclude list of properties type like 'db.DateTimeProperty'

    Returns:
      A checksum in signed integer.
    """
    l = []
    for key, prop in model.properties().iteritems():
        if not (key in exclude_keys) and \
               not any([True for x in exclude_properties if isinstance(prop, x)]):
            l.append(getattr(ref, key))
    return checksum_from_list(l)

def checksum_from_list(l):
    """Returns a checksum from a list of data into an int."""
    return reduce(lambda x,y : x^y, [hash(repr(x)) for x in l])

Note: For the base36 implementation: http://en.wikipedia.org/wiki/Base_36#Python_implementation

Edit: I removed the return in base36, now these functions run without dependences. (An advice from @Skirmantas)


Didn't work with GAE but in same situation I'd use something like:

entity = Entity.get_by_key_name("foobar")
prev_entity_state = deepcopy(entity.__dict__)

if phase_of_moon() == "full":
   entity.werewolf = True
if random.choice([True, False]):
   entity.lucky = True
if some_complicated_condition:
   entity.answer = 42

if entity.__dict__ == prev_entity_state:
    entity.put()
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜