Je veux obtenir un objet de la base de données s'il existe déjà (en fonction des paramètres fournis) ou le créer si ce n'est pas le cas.
Django get_or_create
(ou source ) le fait. Existe-t-il un raccourci équivalent dans SQLAlchemy?
Je l'écris actuellement explicitement comme ceci:
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
la source
la source
session.merge
: stackoverflow.com/questions/12297156/...Réponses:
C'est fondamentalement la façon de le faire, il n'y a pas de raccourci facilement disponible AFAIK.
Vous pouvez bien sûr généraliser:
la source
try...except IntegrityError: instance = session.Query(...)
autour dusession.add
bloc.Suite à la solution de @WoLpH, voici le code qui a fonctionné pour moi (version simple):
Avec cela, je suis capable de get_or_create n'importe quel objet de mon modèle.
Supposons que mon objet modèle soit:
Pour obtenir ou créer mon objet, j'écris:
la source
commit
(ou du moins d'utiliser uniquement un à laflush
place). Cela laisse le contrôle de session à l'appelant de cette méthode et ne risque pas d'émettre une validation prématurée. En outre, utiliserone_or_none()
au lieu defirst()
peut être légèrement plus sûr.J'ai joué avec ce problème et j'ai fini avec une solution assez robuste:
Je viens d'écrire un article de blog assez volumineux sur tous les détails, mais quelques idées assez détaillées sur les raisons pour lesquelles j'ai utilisé cela.
Il se décompresse en un tuple qui vous indique si l'objet existait ou non. Cela peut souvent être utile dans votre flux de travail.
La fonction donne la possibilité de travailler avec des
@classmethod
fonctions de créateur décorées (et des attributs qui leur sont spécifiques).La solution protège contre les conditions de concurrence lorsque vous avez plus d'un processus connecté à la banque de données.
EDIT: Je l' ai changé
session.commit()
pour ,session.flush()
comme expliqué dans ce billet de blog . Notez que ces décisions sont spécifiques à la banque de données utilisée (Postgres dans ce cas).EDIT 2: J'ai mis à jour en utilisant un {} comme valeur par défaut dans la fonction car c'est un gotcha typique de Python. Merci pour le commentaire , Nigel! Si vous êtes curieux de savoir ce que vous en pensez , consultez cette question StackOverflow et cet article de blog .
la source
get_or_create
n'est pas thread-safe. Ce n'est pas atomique. De plus, Djangoget_or_create
renvoie un drapeau True si l'instance a été créée ou un drapeau False dans le cas contraire.get_or_create
il fait presque exactement la même chose. Cette solution renvoie également l'True/False
indicateur pour signaler si l'objet a été créé ou récupéré, et n'est pas non plus atomique. Cependant, la sécurité des threads et les mises à jour atomiques sont une préoccupation pour la base de données, pas pour Django, Flask ou SQLAlchemy, et dans cette solution et Django, sont résolues par des transactions sur la base de données.IntegrityError
cas ne devrait-il pas revenirFalse
puisque ce client n'a pas créé l'objet?Une version modifiée de l'excellente réponse d'Erik
create_method
. Si l'objet créé a des relations et que des membres lui sont affectés via ces relations, il est automatiquement ajouté à la session. Par exemple, créez unbook
, qui auser_id
etuser
comme relation correspondante, puis faire à l'book.user=<user object>
intérieur decreate_method
ajouterabook
à la session. Cela signifie qu'ilcreate_method
doit être à l'intérieurwith
pour bénéficier d'un éventuel retour en arrière. Notez quebegin_nested
déclenche automatiquement un flush.Notez que si vous utilisez MySQL, le niveau d'isolation des transactions doit être défini sur
READ COMMITTED
plutôt queREPEATABLE READ
pour que cela fonctionne. Get_or_create de Django (et ici ) utilise le même stratagème, voir aussi la documentation Django .la source
IntegrityError
nouvelle requête peut toujours échouerNoResultFound
avec le niveau d'isolement par défaut de MySQLREPEATABLE READ
si la session avait précédemment interrogé le modèle dans la même transaction. La meilleure solution que je pourrais trouver est d'appelersession.commit()
avant cette requête, ce qui n'est pas non plus idéal car l'utilisateur peut ne pas s'y attendre. La réponse référencée n'a pas ce problème puisque la session.rollback () a le même effet de démarrer une nouvelle transaction.commit
intérieur de cette fonction est sans doute pire que de faire unrollback
, même si pour des cas d'utilisation spécifiques, cela peut être acceptable.commit()
pas le faire. Si ma compréhension du code est correcte, c'est ce que fait 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
SAVEPOINT` influence les lectures avecREPEATABLE READ
. Si aucun effet alors la situation semble irréversible, si effet alors la toute dernière requête pourrait être imbriquée?READ COMMITED
, je devrais peut-être repenser ma décision de ne pas toucher aux valeurs par défaut de la base de données. J'ai testé que la restauration d'unSAVEPOINT
avant qu'une requête ne soit effectuée donne l'impression que cette requête ne s'est jamais produiteREPEATABLE READ
. Par conséquent, j'ai trouvé nécessaire d'inclure la requête dans la clause try dans une transaction imbriquée afin que la requête de laIntegrityError
clause except puisse fonctionner du tout.Cette recette SQLALchemy fait le travail et élégant.
La première chose à faire est de définir une fonction qui reçoit une Session avec laquelle travailler, et associe un dictionnaire à Session () qui garde la trace des clés uniques actuelles .
Un exemple d'utilisation de cette fonction serait dans un mixin:
Et enfin la création du modèle unique get_or_create:
La recette approfondit l'idée et propose différentes approches, mais j'ai utilisé celle-ci avec beaucoup de succès.
la source
Le plus proche sémantiquement est probablement:
Je ne sais pas à quel point il est casher de s'appuyer sur une définition globale
Session
dans sqlalchemy, mais la version Django ne prend pas de connexion alors ...Le tuple retourné contient l'instance et un booléen indiquant si l'instance a été créée (c'est-à-dire qu'il est faux si nous lisons l'instance à partir de la base de données).
Django
get_or_create
est souvent utilisé pour s'assurer que les données globales sont disponibles, donc je m'engage le plus tôt possible.la source
scoped_session
, qui devrait implémenter la gestion de session thread-safe (cela existait-il en 2014?).J'ai légèrement simplifié @Kevin. solution pour éviter d'encapsuler toute la fonction dans une instruction
if
/else
. De cette façon, il n'y en a qu'unreturn
, que je trouve plus propre:la source
Selon le niveau d'isolement que vous avez adopté, aucune des solutions ci-dessus ne fonctionnerait. La meilleure solution que j'ai trouvée est un RAW SQL sous la forme suivante:
Ceci est transactionnellement sûr quels que soient le niveau d'isolement et le degré de parallélisme.
Attention: pour le rendre efficace, il serait judicieux d'avoir un INDEX pour la colonne unique.
la source