Quiero obtener un objeto de la base de datos si ya existe (según los parámetros proporcionados) o crearlo si no existe.
Django get_or_create
(o fuente ) hace esto. ¿Hay un atajo equivalente en SQLAlchemy?
Actualmente lo estoy escribiendo explícitamente así:
def get_or_create_instrument(session, serial_number):
instrument = session.query(Instrument).filter_by(serial_number=serial_number).first()
if instrument:
return instrument
else:
instrument = Instrument(serial_number)
session.add(instrument)
return instrument
python
django
sqlalchemy
FogleBird
fuente
fuente
session.merge
: stackoverflow.com/questions/12297156/…Respuestas:
Esa es básicamente la forma de hacerlo, no hay atajos disponibles AFAIK.
Puedes generalizarlo, por supuesto:
fuente
try...except IntegrityError: instance = session.Query(...)
vuelta a lasession.add
manzana.Siguiendo la solución de @WoLpH, este es el código que funcionó para mí (versión simple):
Con esto, puedo obtener o crear cualquier objeto de mi modelo.
Supongamos que mi objeto modelo es:
Para obtener o crear mi objeto escribo:
fuente
commit
(o al menos usar solo unflush
en su lugar). Esto deja el control de la sesión a la persona que llama de este método y no se arriesgará a emitir una confirmación prematura. Además, usar enone_or_none()
lugar defirst()
podría ser un poco más seguro.He estado jugando con este problema y terminé con una solución bastante sólida:
Acabo de escribir una publicación de blog bastante expansiva sobre todos los detalles, pero algunas ideas de por qué usé esto.
Se descomprime en una tupla que te dice si el objeto existió o no. Esto a menudo puede ser útil en su flujo de trabajo.
La función brinda la capacidad de trabajar con
@classmethod
funciones de creador decoradas (y atributos específicos para ellas).La solución protege contra las condiciones de carrera cuando tienes más de un proceso conectado al almacén de datos.
EDITAR: He cambiado
session.commit()
asession.flush()
como se explica en esta publicación de blog . Tenga en cuenta que estas decisiones son específicas del almacén de datos utilizado (Postgres en este caso).EDIT 2: He actualizado usando un {} como valor predeterminado en la función, ya que este es el típico problema de Python. Gracias por el comentario , Nigel! Si tiene curiosidad sobre este problema, consulte esta pregunta de StackOverflow y esta publicación de blog .
fuente
get_or_create
es seguro para subprocesos. No es atómico. Además, Django's devuelve un indicador True si se creó la instancia o un indicador False de lo contrario.get_or_create
get_or_create
hace casi exactamente lo mismo. Esta solución también devuelve elTrue/False
indicador para indicar si el objeto fue creado o recuperado, y tampoco es atómico. Sin embargo, las actualizaciones atómicas y de seguridad de subprocesos son una preocupación para la base de datos, no para Django, Flask o SQLAlchemy, y tanto en esta solución como en Django, se resuelven mediante transacciones en la base de datos.IntegrityError
volver el casoFalse
ya que este cliente no creó el objeto?Una versión modificada de la excelente respuesta de erik
create_method
. Si el objeto creado tiene relaciones y se le asignan miembros a través de esas relaciones, se agrega automáticamente a la sesión. Por ejemplo, crear unbook
, que tieneuser_id
yuser
como relación correspondiente, luego hacerbook.user=<user object>
dentro decreate_method
agregarábook
a la sesión. Esto significa quecreate_method
debe estar adentrowith
para beneficiarse de una eventual reversión. Tenga en cuenta quebegin_nested
activa automáticamente una descarga.Tenga en cuenta que si usa MySQL, el nivel de aislamiento de la transacción debe establecerse en
READ COMMITTED
lugar de queREPEATABLE READ
esto funcione. Get_or_create de Django (y aquí ) usa la misma estratagema, consulte también la documentación de Django .fuente
IntegrityError
nueva consulta aún puede fallarNoResultFound
con el nivel de aislamiento predeterminado de MySQLREPEATABLE READ
si la sesión había consultado previamente el modelo en la misma transacción. La mejor solución que se me ocurre es llamarsession.commit()
antes de esta consulta, lo que tampoco es ideal ya que el usuario puede no esperarlo. La respuesta a la que se hace referencia no tiene este problema ya que session.rollback () tiene el mismo efecto de comenzar una nueva transacción.commit
dentro de esta función podría decirse que es peor que hacer unarollback
, aunque para casos de uso específicos puede ser aceptable.commit()
lo hace. Si mi comprensión del código es correcta, esto es lo que hace Django., so it does not look like they try to handle this. Looking at the [source](https://github.com/django/django/blob/master/django/db/models/query.py#L491) confirms this. I'm not sure I understand your reply, you mean the user should put his/her query in a nested transaction? It's not clear to me how a
lecturas de influencias `READ COMMITTED SAVEPOINT`REPEATABLE READ
. Si no tiene ningún efecto, la situación parece insalvable, si tiene efecto, ¿podría anidarse la última consulta?READ COMMITED
, tal vez debería repensar mi decisión de no tocar los valores predeterminados de la base de datos. He probado que la restauración de unaSAVEPOINT
antes de que se realizara una consulta hace que esa consulta nunca sucedaREPEATABLE READ
. Por lo tanto, me pareció necesario incluir la consulta en la cláusula try en una transacción anidada para que la consulta en laIntegrityError
cláusula except pueda funcionar.Esta receta de SQLALchemy hace el trabajo agradable y elegante.
Lo primero que debe hacer es definir una función a la que se le asigna una sesión para trabajar, y asocia un diccionario con la sesión () que realiza un seguimiento de las claves únicas actuales .
Un ejemplo de utilización de esta función sería en un mixin:
Y finalmente creando el modelo get_or_create único:
La receta profundiza en la idea y ofrece diferentes enfoques, pero he usado esta con gran éxito.
fuente
La semántica más cercana es probablemente:
no estoy seguro de cuán kosher es confiar en una definición global
Session
en sqlalchemy, pero la versión de Django no toma una conexión, así que ...La tupla devuelta contiene la instancia y un booleano que indica si se creó la instancia (es decir, es Falso si leemos la instancia de la base de datos).
Django a
get_or_create
menudo se usa para asegurarse de que haya datos globales disponibles, por lo que me comprometo lo antes posible.fuente
scoped_session
, lo que debería implementar la administración de sesión segura para subprocesos (¿existía esto en 2014?).Simplifiqué un poco a @Kevin. solución para evitar envolver toda la función en una declaración
if
/else
. De esta manera solo hay unoreturn
, que encuentro más limpio:fuente
Dependiendo del nivel de aislamiento que adoptó, ninguna de las soluciones anteriores funcionaría. La mejor solución que he encontrado es un SQL RAW en la siguiente forma:
Esto es transaccionalmente seguro sea cual sea el nivel de aislamiento y el grado de paralelismo.
Cuidado: para que sea eficiente, sería conveniente tener un ÍNDICE para la columna única.
fuente