SQLAlchemy confirma los cambios en el objeto modificado a través de __dict__

Estoy desarrollando un juego multijugador. Cuando uso un objeto del inventario, debería actualizar las estadísticas de la criatura del usuario con los valores de los atributos de un objeto.

Este es mi código:

try: obj = self._get_obj_by_id(self.query['ObjectID']).first() # Get user's current creature cur_creature = self.user.get_current_creature() # Applying object attributes to user attributes for attribute in obj.attributes: cur_creature.__dict__[str(attribute.Name)] += attribute.Value dbObjs.session.commit() except (KeyError, AttributeError) as err: self.query_failed(err) 

Ahora, esto no compromete las cosas correctamente por alguna razón, así que lo intenté:

 cur_creature.Health = 100 logging.warning(cur_creature.Health) dbObjs.session.commit() 

Lo que funciona, pero no es muy conveniente (ya que necesitaría una gran statement if para actualizar las diferentes estadísticas de la criatura)

Así que intenté:

 cur_creature.__dict__['Health'] = 100 logging.warning(cur_creature.Health) dbObjs.session.commit() 

Obtengo 100 en registros, pero no hay cambios, así que probé:

 cur_creature.__dict__['Health'] = 100 cur_creature.Health = cur_creature.__dict__['Health'] logging.warning(cur_creature.Health) dbObjs.session.commit() 

Todavía ‘100’ en los registros, pero no hay cambios, así que intenté:

 cur_creature.__dict__['Health'] = 100 cur_creature.Health = 100 logging.warning(cur_creature.Health) dbObjs.session.commit() 

Lo que aún escribe 100 en los registros, pero no confirma los cambios en la base de datos. Ahora, esto es raro, ya que solo se diferencia por la versión de trabajo por el hecho de que tiene esta línea en la parte superior:

 cur_creature.__dict__['Health'] = 100 

Resumen: si modifico un atributo directamente, confirmarlo funciona bien. En cambio, si modifico un atributo a través del diccionario de la clase, entonces, no importa cómo lo modifique después, no confirma los cambios en la db.

¿Algunas ideas?

Gracias por adelantado

ACTUALIZACIÓN 1:

Además, esto actualiza la salud en la base de datos, pero no el hambre:

 cur_creature.__dict__['Hunger'] = 0 cur_creature.Health = 100 cur_creature.Hunger = 0 logging.warning(cur_creature.Health) dbObjs.session.commit() 

Por lo tanto, el solo hecho de acceder al diccionario no es un problema para los atributos en general, pero la modificación de un atributo a través del diccionario evita que se confirmen los cambios en esos atributos.

Actualización 2:

Como una solución temporal, he anulado la función __set_item__(self) en la clase Creatures :

 def __setitem__(self, key, value): if key == "Health": self.Health = value elif key == "Hunger": self.Hunger = value 

De modo que el nuevo código para ‘usar objeto’ es:

 try: obj = self._get_obj_by_id(self.query['ObjectID']).first() # Get user's current creature cur_creature = self.user.get_current_creature() # Applying object attributes to user attributes for attribute in obj.attributes: cur_creature[str(attribute.Name)] += attribute.Value dbObjs.session.commit() except (KeyError, AttributeError) as err: self.query_failed(err) 

Actualización 3:

Al echar un vistazo a las sugerencias en las respuestas, me decidí por esta solución:

En las Creatures

 def __setitem__(self, key, value): if key in self.__dict__: setattr(self, key, value) else: raise KeyError(key) 

En el otro metodo

  # Applying object attributes to user attributes for attribute in obj.attributes: cur_creature[str(attribute.Name)] += attribute.Value 

El problema no reside en SQLAlchemy pero se debe al mecanismo de descriptores de Python. Cada atributo de Column es un descriptor: así es como SQLAlchemy “engancha” la recuperación y modificación del atributo para generar solicitudes de base de datos.

Probemos con un ejemplo más simple:

 class Desc(object): def __get__(self, obj, type=None): print '__get__' def __set__(self, obj, value): print '__set__' class A(object): desc = Desc() a = A() a.desc # prints '__get__' a.desc = 2 # prints '__set__' 

Sin embargo, si pasa por a diccionario de instancia y configura otro valor para 'desc' , omite el protocolo del descriptor (consulte Invocar descriptores ):

 a.__dict__['desc'] = 0 # Does not print anything ! 

Aquí, acabamos de crear un nuevo atributo de instancia llamado 'desc' con un valor de 0. El método Desc.__set__ nunca fue llamado, y en su caso SQLAlchemy no tendría la oportunidad de ‘capturar’ la tarea.

La solución es usar setattr , que es exactamente equivalente a escribir a.desc :

 setattr(a, 'desc', 1) # Prints '__set__' 

No uses __dict__ . Use getattr y setattr para modificar los atributos por nombre:

 for attribute in obj.attributes: setattr(cur_creature,str(attribute.Name), getattr(cur_creature,str(attribute.Name)) + attribute.Value) 

Más información:

setattr

getattr