I have to use SQLalchemy Core expression to fetch objects because ORM can't do "update and returning". (the update in ORM doesn't has returning
)
from sqlalchemy import update
class User(ORMBase):
...
# pure sql expression, the object returned is not ORM object.
# the object is a RowProxy.
object = update(User) \
.values({'name': 'Wayne'}) \
.where(User.id == subquery.as_scalar()) \
.returning() \
.fetchone()
When
db_session.add(object)
it report UnmappedInstanceError: Class 'sqlalchemy.engine.result.RowProxy' is not mapped
.
How do I put that RowProxy
object from sql expression into identity map of ORM
?
Possible quick solution: construct the object from kwargs
of your RowProxy
, since those are object-like.
Given:
rowproxy = update(User) \
.values({'name': 'Wayne'}) \
.where(User.id == subquery.as_scalar()) \
.returning() \
.fetchone()
We might be able to do:
user = User(**dict(rowproxy.items()))
rowproxy.items()
returns tuples
of key-value
pairs; dict(...)
converts the tuples
into actual key-value
pairs; and User(...)
takes kwargs
for the model
attribute names.
But what if you have a model
where one of the attribute names
isn't quite the same as the SQL table
column name
? E.g. something like:
class User(ORMBase):
# etc...
user_id = Column(name='id', etc)
When we try to unpack our rowproxy
into the User
class, we'll likely get an error along the lines of: TypeError: 'id' is an invalid keyword argument for User
(because it's expecting user_id
instead).
Now it gets dirty: we should have lying around a mapper
for how to get from the table
attributes to the model
attributes and vice versa:
kw_map = {a.key: a.class_attribute.name for a in User.__mapper__.attrs}
Here, a.key
is the model attribute
(and kwarg
), and a.class_attribute.name
is the table attribute
. This gives us something like:
{
"user_id": "id"
}
Well, we want to actually provide the values we got back from our rowproxy
, which besides allowing object-like access also allows dict-like access:
kwargs = {a.key: rowproxy[a.class_attribute.name] for a in User.__mapper__.attrs}
And now we can do:
user = User(**kwargs)
session.commit()
right after calling update().returning()
to prevent long delays from your changes vs. when they get permanently stored in the database. No need to session.add(user)
later - you already updated()
and just need to commit()
that transactionobject
is a keyword in Python, so try not to stomp on it; you could get some very bizarre behavior doing that; that's why I renamed to rowproxy
.