gribvirus74
gribvirus74

Reputation: 765

How to use SQLite's UPSERT (or ON CONFLICT) with flask_sqlalchemy?

I want to update my SQLite DB with data provided by an external API and I don't want to check every time for any conflicts by myself, instead, I want to take advantage of UPSERT statement. SQLAlchemy documentation for version 1.4 (still in beta but that's ok) shows that this is possible. But I don't know how to get this to work with flask_sqlalchemy.

I've also tried to include sqlite_on_conflict='IGNORE' in constraint definitions (as mentioned in SQLAlchemy docs for version 1.3 here):

class SomeModel(db.Model):
    __table_args__ = (
        db.ForeignKeyConstraint(
            ('id', 'other_id'),
            ('other_table.id', 'other_table.other_id')
            sqlite_on_conflict='IGNORE'
        ),
    )
    # ...

I then verified SQL output with SQLALCHEMY_ECHO set to True and it didn't work at all...

I tried with both 1.3 and 1.4 SQLAlchemy versions.

Upvotes: 8

Views: 4144

Answers (1)

gribvirus74
gribvirus74

Reputation: 765

I found the solution thanks to this gist (credits for droustchev) which I slightly edited. It's a bit messy but it works:

from sqlalchemy.ext.compiler import compiles
from sqlalchemy.sql import Insert


@compiles(Insert, 'sqlite')
def suffix_insert(insert, compiler, **kwargs):
    stmt = compiler.visit_insert(insert, **kwargs)
    if insert.dialect_kwargs.get('sqlite_on_conflict_do_nothing'):
        stmt += ' ON CONFLICT DO NOTHING'
    return stmt


Insert.argument_for('sqlite', 'on_conflict_do_nothing', False)

Just include this code in your models.py file (or whatever filename you have) and then you can write:

some_random_values = [
  {'id': 1, 'some_column': 'asadsadad'},
  {'id': 2, 'some_column': 'dsawaefds'}
]

stmt = db.insert(MyModel, sqlite_on_conflict_do_nothing=True).values(some_random_values)
db.session.execute(stmt)

This should do the trick.

Upvotes: 8

Related Questions