2011-09-07 12 views
12

Un patrón recurrente en mi programación de Python en GAE es obtener una entidad del almacén de datos, y luego posiblemente cambiar esa entidad en función de varias condiciones. Al final, tengo que devolver() la entidad al almacén de datos para garantizar que se guarden los cambios que puedan haberse realizado.Forma elegante de evitar .put() en entidades sin cambios

Sin embargo, a menudo no se realizaron cambios y el .put final() es solo una pérdida de dinero. ¿Cómo puedo asegurarme fácilmente de que solo coloque una entidad si realmente ha cambiado?

El código podría ser algo así como

def handle_get_request(): 
    entity = Entity.get_by_key_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() 

pude mantener una bandera "cambiado", la cual di si cualquier condición cambió la entidad, pero que parece muy frágil. Si olvidé configurarlo en algún lugar, entonces los cambios se perderían.

Lo que terminé usando

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() 

Yo no diría que este "elegante". Elegante sería si el objeto simplemente se guardara automáticamente al final, pero sentí que esto era lo suficientemente simple y fácil de leer por ahora.

+0

¿Qué tal hash la entidad antes y después?Eso por supuesto requiere crear una función hash, que puede o no ser factible para su caso – carlpett

+0

@carlpett Gracias, noté que db.Model ya tiene el método to_xml(), que puedo usar para comparar en lugar de hacer mi propia función hash . – Bemmu

Respuesta

4

¿Por qué no comprobar si el resultado es igual (==) al original y decidir si desea guardarlo? Esto depende de un __eq__ correctamente implementado, pero por defecto una comparación de campo por campo basada en el __dict__ debería hacerlo.

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

(Asegúrese de que el resto de comparación y de hash ricos operadores funcionan correctamente si lo hace See here..)

+0

Supongo que en este caso necesitaría algún tipo de copia profunda del objeto para comparar después de cambios posteriores. – Bemmu

+0

Correcto, y además, como se mencionó, esto depende de un "profundo" __eq__ cuando sea necesario. –

+0

Supongo que en la práctica, la mayoría de los objetos son bastante poco profundos y una copia bastante poco profunda hará el truco. Por supuesto, eso depende de tu gráfico de objetos exacto. –

1

no funcionó con GAE pero en misma situación que haría uso de algo como:

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() 
4

una posible solución es el uso de una envoltura que rastrea cualquier cambio de atributo:

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 
2

Esta respuesta es una parte de una pregunta que publiqué sobre Python checksum of a dict Con las respuestas de esta pregunta desarrollé un método para generar suma de comprobación de a db.Model.

Este es un ejemplo:

>>> 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' 

que definen estos métodos:

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]) 

Nota: Para la aplicación base36: http://en.wikipedia.org/wiki/Base_36#Python_implementation

Edit: Eliminé el retorno en base36, ahora estas funciones se ejecutan sin dependencias. (Un consejo de @Skirmantas)

+0

¿Por qué exactamente base36? Puede usar base64, base32 o base16 desde python standart lib. – Ski

+0

@Skirmantas Sí, puedes usar lo que quieras. Esta fuente proviene de mi implementación y me gusta base36 porque creo que es elegante. – sahid

Cuestiones relacionadas