connect SQLAlchemy ORM with the objects from sql core expression?

做~自己de王妃 提交于 2019-12-04 14:01:45

Simple case:

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.

More difficult case:

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)

Errata:

  • you may want to 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 transaction
  • object 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.

I'm not sure there is a straight-forward way to do what you're describing, which is essentially to build an ORM object that maps directly to an database entry but without performing the query through the ORM.

My intuition is that the naive approach (just build init the ORM object with the values in the database) would just create another row with the same values (or fail to because of uniqueness constraints).

The more standard way to do what you are asking would be to query the row through the ORM first and then update the database from that ORM object.

user = User.query.filter(User.user_attribute == 'foo').one()
user.some_value = 'bar'
session.add(user)
session.commit()

I'm not sure if you have some constraint on your end that prevents you from using that pattern though. The documentation works through similar examples

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!