SQLAlchemy CORE: bindparam with postgresql on_conflict_do_update with a JSONB column

436 views
Skip to first unread message

Massimiliano della Rovere

unread,
Sep 15, 2020, 10:56:00 AM9/15/20
to sqlal...@googlegroups.com
Greetings,
I am using SQLAlchemy==1.3.18.

I have an SQLAlchemy "Settings" table with a "data" column defined as:

from sqlalchemy.dialects.postgresql import JSONB
Settings = Table(
    "settings",
    self._metadata,
    # ...
    Column("data", JSONB, nullable=False))

and later in the code this "baked" query:

from sqlalchemy.dialects.postgresql import insert as pinsert, JSONB
query = (
    pinsert(Settings)
    .values({
        "key": "xxx",
        "data": bindparam("timestamps", type_=JSONB)})
    .on_conflict_do_update(
        index_elements=(Settings.columns.key,),
        set_=bindparam("timestamps", type_=JSONB))

The python interpreter throws me the following error raised by sqlalchemy.dialects.postgresql.dml line 227:
        if not isinstance(set_, dict) or not set_:
            raise ValueError("set parameter must be a non-empty dictionary")

it seems that the "set_" parameter of the "on_conflict_do_update" method requires a dict, and thus is not compatible with bindparam.
I have also tried to set the "type_" parameter of bindparam to dict, but I did not solve the problem.

Please, can anybody help me?

Mike Bayer

unread,
Sep 15, 2020, 11:13:08 AM9/15/20
to noreply-spamdigest via sqlalchemy
the dictionary is with column names as keys:


set_={"data": bindparam("timestamps")}

hope this helps
--
SQLAlchemy -
The Python SQL Toolkit and Object Relational Mapper
 
 
To post example code, please provide an MCVE: Minimal, Complete, and Verifiable Example. See http://stackoverflow.com/help/mcve for a full description.
---
You received this message because you are subscribed to the Google Groups "sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email to sqlalchemy+...@googlegroups.com.

Massimiliano della Rovere

unread,
Sep 15, 2020, 11:34:27 AM9/15/20
to sqlal...@googlegroups.com
Yes, Mike, that solved the problem! I forgot to pass the column name.

Now I get a different error: bindparam is not json-serializable.
StatementError('(builtins.TypeError) Object of type BindParameter is not JSON serializable'),
  
Is the JSONB column not supporting lazy/bindparam-compatible "preparing" of queries?

Mike Bayer

unread,
Sep 15, 2020, 11:51:11 AM9/15/20
to noreply-spamdigest via sqlalchemy
this indicates a bindparam() object is being interpreted as a value somewhere but I can't reproduce that. 

can you provide an MCVE ?

Mike Bayer

unread,
Sep 15, 2020, 11:56:13 AM9/15/20
to noreply-spamdigest via sqlalchemy
here's one based on your samples of code, works fine on this end, you can use this to see what you are doing differently.

from sqlalchemy import bindparam
from sqlalchemy import Column
from sqlalchemy import create_engine
from sqlalchemy import MetaData
from sqlalchemy import String
from sqlalchemy import Table
from sqlalchemy.dialects.postgresql import insert as pinsert
from sqlalchemy.dialects.postgresql import JSONB

m = MetaData()

Settings = Table(
    "settings",
    m,
    Column("key", String, primary_key=True),
    Column("data", JSONB, nullable=False),
)

e = create_engine("postgresql://scott:tiger@localhost/test", echo=True)
m.drop_all(e)
m.create_all(e)

query = (
    pinsert(Settings)
    .values({"key": "xxx", "data": bindparam("timestamps", type_=JSONB)})
    .on_conflict_do_update(
        index_elements=(Settings.columns.key,),
        set_={"data": bindparam("timestamps", type_=JSONB)},
    )
)

with e.begin() as conn:
    conn.execute(query, {"timestamps": {"foo": "bar"}})

Massimiliano della Rovere

unread,
Sep 15, 2020, 1:40:22 PM9/15/20
to sqlal...@googlegroups.com
I removed all the tables from the DB and recreated them all (Settings among them) with alembic and everything worked.
Thanks for your help, Mike!

Reply all
Reply to author
Forward
0 new messages