diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2021-07-02 11:23:20 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2021-07-09 15:55:04 -0400 |
commit | 39292ca60d1642cfa12cd9bb9dd7016fb5f0132c (patch) | |
tree | e69fa1f3a82d63c8b5371d0e302b3ad0296e1019 /test/ext/asyncio/test_session_py3k.py | |
parent | 7ed6aee2750d6ceaadc429087e2314962808180a (diff) | |
download | sqlalchemy-39292ca60d1642cfa12cd9bb9dd7016fb5f0132c.tar.gz |
implement deferred scalarobject history load
Modified the approach used for history tracking of scalar object
relationships that are not many-to-one, i.e. one-to-one relationships that
would otherwise be one-to-many. When replacing a one-to-one value, the
"old" value that would be replaced is no longer loaded immediately, and is
instead handled during the flush process. This eliminates an historically
troublesome lazy load that otherwise often occurs when assigning to a
one-to-one attribute, and is particularly troublesome when using
"lazy='raise'" as well as asyncio use cases.
This change does cause a behavioral change within the
:meth:`_orm.AttributeEvents.set` event, which is nonetheless currently
documented, which is that the event applied to such a one-to-one attribute
will no longer receive the "old" parameter if it is unloaded and the
:paramref:`_orm.relationship.active_history` flag is not set. As is
documented in :meth:`_orm.AttributeEvents.set`, if the event handler needs
to receive the "old" value when the event fires off, the active_history
flag must be established either with the event listener or with the
relationship. This is already the behavior with other kinds of attributes
such as many-to-one and column value references.
The change additionally will defer updating a backref on the "old" value
in the less common case that the "old" value is locally present in the
session, but isn't loaded on the relationship in question, until the
next flush occurs. If this causes an issue, again the normal
:paramref:`_orm.relationship.active_history` flag can be set to ``True``
on the relationship.
A private flag which restores the old value is retained for now,
as support within relevant test suites to exercise the old and
new behaviors together. This is so that if the behavioral change
produces problems we have test harnesses set up to further examine these
behaviors. The "legacy" style can go away in 2.0 or in a much later
1.4 release.
Fixes: #6708
Change-Id: Id7f72fc39dcbec9119b665e528667a9919bb73b4
Diffstat (limited to 'test/ext/asyncio/test_session_py3k.py')
-rw-r--r-- | test/ext/asyncio/test_session_py3k.py | 90 |
1 files changed, 90 insertions, 0 deletions
diff --git a/test/ext/asyncio/test_session_py3k.py b/test/ext/asyncio/test_session_py3k.py index 1f5c95054..0883cb026 100644 --- a/test/ext/asyncio/test_session_py3k.py +++ b/test/ext/asyncio/test_session_py3k.py @@ -1,7 +1,10 @@ +from sqlalchemy import Column from sqlalchemy import event from sqlalchemy import exc +from sqlalchemy import ForeignKey from sqlalchemy import func from sqlalchemy import inspect +from sqlalchemy import Integer from sqlalchemy import select from sqlalchemy import Table from sqlalchemy import testing @@ -473,6 +476,93 @@ class AsyncCascadesTest(AsyncFixture): ) +class AsyncORMBehaviorsTest(AsyncFixture): + @testing.fixture + def one_to_one_fixture(self, registry, async_engine): + async def go(legacy_inactive_history_style): + @registry.mapped + class A: + __tablename__ = "a" + + id = Column(Integer, primary_key=True) + b = relationship( + "B", + uselist=False, + _legacy_inactive_history_style=( + legacy_inactive_history_style + ), + ) + + @registry.mapped + class B: + __tablename__ = "b" + id = Column(Integer, primary_key=True) + a_id = Column(ForeignKey("a.id")) + + async with async_engine.begin() as conn: + await conn.run_sync(registry.metadata.create_all) + + return A, B + + return go + + @testing.combinations( + ( + "legacy_style", + True, + ), + ( + "new_style", + False, + ), + argnames="_legacy_inactive_history_style", + id_="ia", + ) + @async_test + async def test_new_style_active_history( + self, async_session, one_to_one_fixture, _legacy_inactive_history_style + ): + + A, B = await one_to_one_fixture(_legacy_inactive_history_style) + + a1 = A() + b1 = B() + + a1.b = b1 + async_session.add(a1) + + await async_session.commit() + + b2 = B() + + if _legacy_inactive_history_style: + # aiomysql dialect having problems here, emitting weird + # pytest warnings and we might need to just skip for aiomysql + # here, which is also raising StatementError w/ MissingGreenlet + # inside of it + with testing.expect_raises( + (exc.StatementError, exc.MissingGreenlet) + ): + a1.b = b2 + else: + a1.b = b2 + + await async_session.flush() + + await async_session.refresh(b1) + + eq_( + ( + await async_session.execute( + select(func.count()) + .where(B.id == b1.id) + .where(B.a_id == None) + ) + ).scalar(), + 1, + ) + + class AsyncEventTest(AsyncFixture): """The engine events all run in their normal synchronous context. |