Использование session.query для чтения незафиксированных данных в SQLAlchemy - PullRequest
0 голосов
/ 18 июня 2019

Резюме

Я пытаюсь написать интеграционные тесты для ряда операций с базами данных и хочу использовать сеанс SQLAlchemy в качестве промежуточной среды для проверки и отката транзакции.

Возможно ли получить незафиксированные данные, используя session.query(Foo) вместо session.execute(text('select * from foo'))?

Предпосылки и исследования

Эти результаты были получены с использованием SQLAlchemy 1.2.10, Python 2.7.13 и Postgres 9.6.11.

Я просмотрел соответствующие записи в StackOverflow, но не нашел объяснения, почему две нижеприведенные операции должны вести себя по-разному.

Воспроизводимый пример

1) Я устанавливаю соединение с базой данных и определяю объект модели; пока проблем нет:

from sqlalchemy import text
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, ForeignKey

#####
# Prior DB setup:
# CREATE TABLE foo (id int PRIMARY KEY, label text);
#####

# from https://docs.sqlalchemy.org/en/13/orm/mapping_styles.html#declarative-mapping
Base = declarative_base()

class Foo(Base):
    __tablename__ = 'foo'
    id = Column(Integer, primary_key=True)
    label = Column(String)

# from https://docs.sqlalchemy.org/en/13/orm/session_basics.html#getting-a-session
some_engine = create_engine('postgresql://username:password@endpoint/database')
Session = sessionmaker(bind=some_engine)

2) Я выполняю некоторые обновления, не фиксируя результат, и я могу увидеть промежуточные данные, выполнив оператор select в сеансе:

session = Session()
sql_insert = text("INSERT INTO foo (id, label) VALUES (1, 'original')")
session.execute(sql_insert);
sql_read = text("SELECT * FROM foo WHERE id = 1");
res = session.execute(sql_read).first()
print res.label

sql_update = text("UPDATE foo SET label = 'updated' WHERE id = 1")
session.execute(sql_update)
res2 = session.execute(sql_read).first()
print res2.label

sql_update2 = text("""
INSERT INTO foo (id, label) VALUES (1, 'second_update')
ON CONFLICT (id) DO UPDATE
    SET (label) = (EXCLUDED.label)
""")
session.execute(sql_update2)
res3 = session.execute(sql_read).first()
print res3.label
session.rollback()

# prints expected values: 'original', 'updated', 'second_update'

3) Я пытаюсь заменить операторы select на session.query, но я не вижу новые данные:

session = Session()
sql_insert = text("INSERT INTO foo (id, label) VALUES (1, 'original')")
session.execute(sql_insert);
res = session.query(Foo).filter_by(id=1).first()
print res.label

sql_update = text("UPDATE foo SET label = 'updated' WHERE id = 1")
session.execute(sql_update)
res2 = session.query(Foo).filter_by(id=1).first()
print res2.label

sql_update2 = text("""
INSERT INTO foo (id, label) VALUES (1, 'second_update')
ON CONFLICT (id) DO UPDATE
    SET (label) = (EXCLUDED.label)
""")
session.execute(sql_update2)
res3 = session.query(Foo).filter_by(id=1).first()
print res3.label
session.rollback()
# prints: 'original', 'original', 'original'

Я ожидаю, что напечатанный вывод шага 3 будет 'оригинальным', 'обновленным', 'second_update'.

1 Ответ

1 голос
/ 18 июня 2019

Основная причина в том, что необработанные запросы SQL и ORM в этом случае не смешиваются автоматически. Хотя Session не является кешем , то есть он не кэширует запросы, но сохраняет объекты на основе их первичного ключа в карте идентификации . Когда Query возвращает строку для сопоставленного объекта, возвращается существующий объект. Вот почему вы не наблюдаете изменения, которые вы сделали на третьем шаге. Это может показаться довольно плохим способом справиться с ситуацией, но SQLAlchemy работает, основываясь на некоторых предположениях о изоляции транзакции , как описано в «Когда истечь или обновить» :

Изоляция транзакции

... [Итак], как наилучшее предположение, предполагается, что в рамках транзакции, если не известно, что выражение SQL было отправлено для изменения конкретной строки, нет необходимости обновлять строку, если явно сказал, чтобы сделать это.

Полное примечание об изоляции транзакций заслуживает прочтения. Способ сделать такие изменения известными SQLAlchemy - это выполнить обновления с использованием API запросов, если это возможно, и вручную истечь измененных объектов, если все остальное не удается. Учитывая это, ваш третий шаг может выглядеть так:

session = Session()
sql_insert = text("INSERT INTO foo (id, label) VALUES (1, 'original')")
session.execute(sql_insert);
res = session.query(Foo).filter_by(id=1).first()
print(res.label)

session.query(Foo).filter_by(id=1).update({Foo.label: 'updated'},
                                          synchronize_session='fetch')
# This query is actually redundant, `res` and `res2` are the same object
res2 = session.query(Foo).filter_by(id=1).first()
print(res2.label)

sql_update2 = text("""
INSERT INTO foo (id, label) VALUES (1, 'second_update')
ON CONFLICT (id) DO UPDATE
    SET label = EXCLUDED.label
""")
session.execute(sql_update2)
session.expire(res)
# Again, this query is redundant and fetches the same object that needs
# refreshing anyway
res3 = session.query(Foo).filter_by(id=1).first()
print(res3.label)
session.rollback()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...