Desejo obter um objeto do banco de dados, se ele já existir (com base nos parâmetros fornecidos) ou criá-lo, se não existir.
O Django get_or_create
(ou fonte ) faz isso. Existe um atalho equivalente no SQLAlchemy?
Atualmente, estou escrevendo explicitamente assim:
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
fonte
fonte
session.merge
: stackoverflow.com/questions/12297156/...Respostas:
Essa é basicamente a maneira de fazer isso, não há atalho prontamente disponível para o AFAIK.
Você pode generalizá-lo, é claro:
fonte
try...except IntegrityError: instance = session.Query(...)
volta aosession.add
quarteirão.Seguindo a solução do @WoLpH, este é o código que funcionou para mim (versão simples):
Com isso, eu sou capaz de obter ou criar qualquer objeto do meu modelo.
Suponha que meu objeto de modelo seja:
Para obter ou criar meu objeto, escrevo:
fonte
commit
(ou pelo menos usar apenas umflush
). Isso deixa o controle da sessão para o chamador desse método e não corre o risco de emitir uma confirmação prematura. Além disso, usar emone_or_none()
vez defirst()
pode ser um pouco mais seguro.Eu estou jogando com esse problema e acabei com uma solução bastante robusta:
Acabei de escrever um post bastante amplo sobre todos os detalhes, mas algumas idéias sobre o porquê disso.
Ele é descompactado em uma tupla que informa se o objeto existe ou não. Isso geralmente pode ser útil no seu fluxo de trabalho.
A função permite trabalhar com
@classmethod
funções decoradas do criador (e atributos específicos a elas).A solução protege contra condições de corrida quando você tem mais de um processo conectado ao armazenamento de dados.
EDIT: mudei
session.commit()
parasession.flush()
conforme explicado nesta postagem do blog . Observe que essas decisões são específicas para o armazenamento de dados usado (neste caso, o Postgres).EDIT 2: Eu atualizei usando um {} como valor padrão na função, pois isso é uma pegadinha típica do Python. Obrigado pelo comentário , Nigel! Se você está curioso sobre isso, confira esta pergunta do StackOverflow e esta postagem no blog .
fonte
get_or_create
é seguro para threads. Não é atômico. Além disso, o Django retorna uma flag True se a instância foi criada ou uma flag False caso contrário.get_or_create
get_or_create
, faz quase exatamente a mesma coisa. Essa solução também retorna oTrue/False
sinalizador para sinalizar se o objeto foi criado ou buscado e também não é atômico. No entanto, a segurança de threads e as atualizações atômicas são uma preocupação para o banco de dados, não para o Django, Flask ou SQLAlchemy, e nesta solução e no Django, são resolvidos por transações no banco de dados.IntegrityError
retornar,False
pois esse cliente não criou o objeto?Uma versão modificada da excelente resposta de erik
create_method
. Se o objeto criado tiver relações e receber membros por meio dessas relações, ele será adicionado automaticamente à sessão. Por exemplo, crie umbook
, que tenhauser_id
euser
como relacionamento correspondente, e fazerbook.user=<user object>
dentro delecreate_method
será adicionadobook
à sessão. Isso significa quecreate_method
deve estar dentrowith
para se beneficiar de uma eventual reversão. Observe quebegin_nested
dispara automaticamente um flush.Observe que, se você estiver usando o MySQL, o nível de isolamento da transação deve ser definido como
READ COMMITTED
e nãoREPEATABLE READ
para que isso funcione. O get_or_create do Django (e aqui ) usa o mesmo estratagema, veja também a documentação do Django .fonte
IntegrityError
re-consulta ainda poderá falharNoResultFound
com o nível de isolamento padrão do MySQLREPEATABLE READ
se a sessão tiver consultado anteriormente o modelo na mesma transação. A melhor solução que eu poderia encontrar é ligarsession.commit()
antes dessa consulta, o que também não é o ideal, pois o usuário pode não esperar. A resposta referenciada não tem esse problema, pois o session.rollback () tem o mesmo efeito de iniciar uma nova transação.commit
dentro dessa função, é discutivelmente pior do que fazer arollback
, embora para casos de uso específicos possa ser aceitável.commit()
. Se meu entendimento do código está correto, é isso que o Django faz., 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
influências do `READ COMMITTED SAVEPOINT`REPEATABLE READ
. Se nenhum efeito, então, a situação parece inalcançável; se efeito, a última consulta pode ser aninhada?READ COMMITED
, talvez eu deva repensar minha decisão de não tocar nos padrões do banco de dados. Eu testei que a restauração de umSAVEPOINT
antes de uma consulta ser feita faz com que ela nunca aconteçaREPEATABLE READ
. Portanto, achei necessário incluir a consulta na cláusula try em uma transação aninhada para que a consulta naIntegrityError
cláusula exceto possa funcionar.Esta receita SQLALchemy faz o trabalho agradável e elegante.
A primeira coisa a fazer é definir uma função que recebe uma Sessão para trabalhar e associa um dicionário à Session () que controla as chaves exclusivas atuais .
Um exemplo de utilização dessa função seria em um mixin:
E, finalmente, criando o modelo exclusivo get_or_create:
A receita vai mais fundo na idéia e oferece abordagens diferentes, mas eu usei essa com muito sucesso.
fonte
O mais próximo semanticamente é provavelmente:
não tenho certeza de quão kosher é confiar em um definido globalmente
Session
no sqlalchemy, mas a versão do Django não aceita uma conexão, então ...A tupla retornada contém a instância e um booleano indicando se a instância foi criada (ou seja, é False se lemos a instância no banco de dados).
O Django's
get_or_create
é frequentemente usado para garantir que dados globais estejam disponíveis, por isso estou comprometendo o mais cedo possível.fonte
scoped_session
, o que deve implementar o gerenciamento de sessões com segurança de threads (isso existia em 2014?).Simplifiquei levemente o @Kevin. solução para evitar agrupar toda a função em uma instrução
if
/else
. Desta forma, há apenas umreturn
, que eu acho mais limpo:fonte
Dependendo do nível de isolamento adotado, nenhuma das soluções acima funcionaria. A melhor solução que encontrei é um SQL RAW no seguinte formato:
Isso é transacionalmente seguro, independentemente do nível de isolamento e do grau de paralelismo.
Cuidado: para torná-lo eficiente, seria aconselhável ter um ÍNDICE para a coluna exclusiva.
fonte