подключить SQLAlchemy ORM к объектам из выражения ядра sql?

Я должен использовать выражение SQLalchemy Core для извлечения объектов, потому что ORM не может выполнять «обновление и возврат». (обновление в ORM не имеет 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()

Когда

db_session.add(object)

это отчет UnmappedInstanceError: Class 'sqlalchemy.engine.result.RowProxy' is not mapped.

Как поместить этот объект RowProxy из выражения sql в карту идентификации ORM?


person WeiChing 林煒清    schedule 07.01.2017    source источник
comment
Похоже, что SQLAlchemy упустил из виду, что что-то вроде update-returning, которое (обычно) возвращает всю обновляемую таблицу, не имеет простого способа сопоставления с объектами. К сожалению, для этого нет более счастливого ответа.   -  person dwanderson    schedule 10.11.2017
comment
Вы можете получить некоторую выгоду от: obj = User(**dict(object.items())) но, похоже, это работает не во всех случаях.   -  person dwanderson    schedule 10.11.2017


Ответы (2)


Простой случай:

Возможное быстрое решение: построить объект из kwargs ваших RowProxy, так как они объектно-подобны.

Данный:

rowproxy = update(User)  \
    .values({'name': 'Wayne'})  \
    .where(User.id == subquery.as_scalar()) \
    .returning() \
    .fetchone()

Мы могли бы сделать:

user = User(**dict(rowproxy.items()))

rowproxy.items() возвращает tuples из key-value пар; dict(...) преобразует tuples в фактические key-value пар; и User(...) принимает kwargs для имен атрибутов model.

Более сложный случай:

Но что, если у вас есть model, где один из attribute names не совсем совпадает с SQL table column name? Например. что-то типа:

class User(ORMBase):
    # etc...
    user_id = Column(name='id', etc)

Когда мы попытаемся распаковать наш rowproxy в класс User, мы, скорее всего, получим ошибку следующего содержания: TypeError: 'id' is an invalid keyword argument for User (потому что вместо этого ожидается user_id).

Теперь это становится грязным: у нас должен лежать mapper для того, как перейти от атрибутов table к атрибутам model и наоборот:

kw_map = {a.key: a.class_attribute.name for a in User.__mapper__.attrs}

Здесь a.key — это model attributekwarg), а a.class_attribute.name — это table attribute. Это дает нам что-то вроде:

{
    "user_id": "id"
}

Что ж, мы хотим фактически предоставить значения, которые мы получили от нашего rowproxy, который, помимо предоставления объектного доступа, также позволяет доступ, подобный dict:

kwargs = {a.key: rowproxy[a.class_attribute.name] for a in User.__mapper__.attrs}

И теперь мы можем сделать:

user = User(**kwargs)

Исправления:

  • вы можете захотеть session.commit() сразу после вызова update().returning(), чтобы предотвратить длительные задержки ваших изменений по сравнению с тем, когда они постоянно сохраняются в базе данных. Нет необходимости session.add(user) позже — вы уже updated() и вам просто нужно commit() эту транзакцию
  • object — это ключевое слово в Python, поэтому старайтесь не топтать его; при этом вы можете получить очень странное поведение; вот почему я переименовал в rowproxy.
person dwanderson    schedule 09.11.2017

Я не уверен, что существует прямой способ сделать то, что вы описываете, который, по сути, состоит в создании объекта ORM, который напрямую сопоставляется с записью базы данных, но без выполнения запроса через ORM.

Моя интуиция такова, что наивный подход (просто построить объект ORM со значениями в базе данных) просто создаст другую строку с теми же значениями (или не сможет из-за ограничений уникальности).

Более стандартный способ сделать то, что вы просите, — запросить строку через ORM сначала, а затем обновить базу данных из этого объекта ORM.

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

Я не уверен, есть ли у вас какое-то ограничение на вашем конце, которое мешает вам использовать этот шаблон. Документация работает через аналогичные примеры

person ACV    schedule 09.01.2017