python - many - SQLAlchemy DetachedInstanceError con atributo regular(no una relación)
sqlalchemy many to many (4)
Acabo de empezar a usar SQLAlchemy y obtener un DetachedInstanceError y no puedo encontrar mucha información sobre esto en ninguna parte. Estoy usando la instancia fuera de una sesión, por lo que es natural que SQLAlchemy no pueda cargar ninguna relación si no están cargados, sin embargo, el atributo al que estoy accediendo no es una relación, de hecho este objeto no tiene ninguna relación. Encontré soluciones como la carga ansiosa, pero no puedo aplicarme a esto porque esta no es una relación. Incluso intenté "tocar" este atributo antes de cerrar la sesión, pero aún así no impide la excepción. ¿Qué podría estar causando esta excepción para una propiedad no relacional incluso después de haber sido accedida con éxito una vez antes? Se agradece cualquier ayuda para depurar este problema. Mientras tanto intentaré obtener un escenario independiente reproducible y actualizar aquí.
Actualización: este es el mensaje de excepción real con algunas pilas:
File "/home/hari/bin/lib/python2.6/site-packages/SQLAlchemy-0.6.1-py2.6.egg/sqlalchemy/orm/attributes.py", line 159, in __get__
return self.impl.get(instance_state(instance), instance_dict(instance))
File "/home/hari/bin/lib/python2.6/site-packages/SQLAlchemy-0.6.1-py2.6.egg/sqlalchemy/orm/attributes.py", line 377, in get
value = callable_(passive=passive)
File "/home/hari/bin/lib/python2.6/site-packages/SQLAlchemy-0.6.1-py2.6.egg/sqlalchemy/orm/state.py", line 280, in __call__
self.manager.deferred_scalar_loader(self, toload)
File "/home/hari/bin/lib/python2.6/site-packages/SQLAlchemy-0.6.1-py2.6.egg/sqlalchemy/orm/mapper.py", line 2323, in _load_scalar_attributes
(state_str(state)))
DetachedInstanceError: Instance <ReportingJob at 0xa41cd8c> is not bound to a Session; attribute refresh operation cannot proceed
El modelo parcial se ve así:
metadata = MetaData()
ModelBase = declarative_base(metadata=metadata)
class ReportingJob(ModelBase):
__tablename__ = ''reporting_job''
job_id = Column(BigInteger, Sequence(''job_id_sequence''), primary_key=True)
client_id = Column(BigInteger, nullable=True)
Y el campo client_id es lo que está causando esta excepción con un uso como el siguiente:
Consulta:
jobs = session /
.query(ReportingJob) /
.filter(ReportingJob.job_id == job_id) /
.all()
if jobs:
# FIXME(Hari): Workaround for the attribute getting lazy-loaded.
jobs[0].client_id
return jobs[0]
Esto es lo que desencadena la excepción más tarde fuera del alcance de la sesión:
msg = msg + ", client_id: %s" % job.client_id
En cuanto a mí (novato), cometí un error en la sangría y cerré la sesión dentro de mi ciclo, en la que bucle cada fila, realizo algunas operaciones y me comprometo cada vez.
Entonces, para los novatos como yo, revise su código antes de configurar cosas como expire_on_commit=False
, puede llevarlo a otra trampa.
Encontré la causa raíz al intentar reducir el código que causó la excepción. Coloqué el mismo código de acceso de atributo en diferentes lugares después de cerrar la sesión y encontré que definitivamente no causa ningún problema inmediatamente después del cierre de la sesión de consulta. Resulta que el problema comienza a aparecer después de cerrar una nueva sesión que se abre para actualizar el objeto. Una vez que entendí que el estado del objeto no se puede usar después de cerrar una sesión, pude encontrar este thread que discutió este mismo tema. Dos soluciones que salen del hilo son:
- Mantener una sesión abierta (lo cual es obvio)
- Especifique
expire_on_commit=False
asessionmaker()
.
La tercera opción es establecer manualmente expire_on_commit
en False
en la sesión una vez que se haya creado, algo así como: session.expire_on_commit = False
. Verifiqué que esto resuelve mi problema.
Obtenemos errores similares, incluso con expire_on_commit
establecido en False
. Al final, fue causado por dos sessionmaker
que se estaban acostumbrando a hacer sesiones en diferentes solicitudes. Realmente no entiendo lo que estaba pasando, pero si ve esta excepción con expire_on_commit=False
, asegúrese de que no haya inicializado dos sessionmaker
s.
Tuve un problema similar con DetachedInstanceError: Instance <> is not bound to a Session;
La situación era bastante simple, pasé la sesión y el registro para actualizar a mi función y fusionaría el registro y lo comprometería con la base de datos. En la primera muestra obtendría el error, ya que era flojo y pensé que podía simplemente devolver el objeto fusionado para que mi registro operativo se actualizase (es decir, su valor is_modified
sería falso). is_modified
registro actualizado e is_modified
ahora era falso, pero los usos posteriores arrojaron el error. Creo que esto se agravó debido a los registros secundarios relacionados, pero no del todo seguro de eso.
def EditStaff(self, session, record):
try:
r = session.merge(record)
session.commit()
return r
except:
return False
Después de mucho googlear y leer sobre sesiones, etc., me di cuenta de que como había capturado la instancia r antes de la confirmación y la devolvía, cuando ese mismo registro se enviaba a esta función para otra edición / confirmación, había perdido su sesión.
Entonces, para solucionar esto solo consulto la base de datos para el registro que acaba de actualizar y la devuelvo para mantenerla en sesión y marque su valor is_modified
nuevo en falso.
def EditStaff(self, session, record):
try:
session.merge(record)
session.commit()
r = self.GetStaff(session, record)
return r
except:
return False
Establecer expire_on_commit=False
también evitó el error como se mencionó anteriormente, pero no creo que realmente aborde el error, y podría conducir a muchos otros problemas de la OMI.