diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2021-05-04 11:19:00 -0400 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2021-05-04 11:25:12 -0400 |
| commit | dd6f4543bc8ccbf07bfc5c8fb850be60ab420b57 (patch) | |
| tree | 19a5c09bb4245b9b430a214084cdbc93a0ece510 | |
| parent | f1f4f466fd809a14bff6d0c405a1d5da87438379 (diff) | |
| download | sqlalchemy-dd6f4543bc8ccbf07bfc5c8fb850be60ab420b57.tar.gz | |
Restore detached object logic for dynamic, but warn
Fixed regression involving ``lazy='dynamic'`` loader in conjunction with a
detached object. The previous behavior was that the dynamic loader upon
calling methods like ``.all()`` returns empty lists for detached objects
without error, this has been restored; however a warning is now emitted as
this is not the correct result. Other dynamic loader scenarios correctly
raise ``DetachedInstanceError``.
Fixes: #6426
Change-Id: Id7ad204bef947491fa7e462c5acda2055fada910
| -rw-r--r-- | doc/build/changelog/unreleased_14/6426.rst | 10 | ||||
| -rw-r--r-- | lib/sqlalchemy/engine/result.py | 9 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/dynamic.py | 22 | ||||
| -rw-r--r-- | test/orm/test_dynamic.py | 31 |
4 files changed, 66 insertions, 6 deletions
diff --git a/doc/build/changelog/unreleased_14/6426.rst b/doc/build/changelog/unreleased_14/6426.rst new file mode 100644 index 000000000..d0a3cc28e --- /dev/null +++ b/doc/build/changelog/unreleased_14/6426.rst @@ -0,0 +1,10 @@ +.. change:: + :tags: bug, regression, orm + :tickets: 6426 + + Fixed regression involving ``lazy='dynamic'`` loader in conjunction with a + detached object. The previous behavior was that the dynamic loader upon + calling methods like ``.all()`` returns empty lists for detached objects + without error, this has been restored; however a warning is now emitted as + this is not the correct result. Other dynamic loader scenarios correctly + raise ``DetachedInstanceError``. diff --git a/lib/sqlalchemy/engine/result.py b/lib/sqlalchemy/engine/result.py index f02ceff15..a80395941 100644 --- a/lib/sqlalchemy/engine/result.py +++ b/lib/sqlalchemy/engine/result.py @@ -1593,10 +1593,17 @@ class IteratorResult(Result): """ - def __init__(self, cursor_metadata, iterator, raw=None): + def __init__( + self, + cursor_metadata, + iterator, + raw=None, + _source_supports_scalars=False, + ): self._metadata = cursor_metadata self.iterator = iterator self.raw = raw + self._source_supports_scalars = _source_supports_scalars def _soft_close(self, **kw): self.iterator = iter([]) diff --git a/lib/sqlalchemy/orm/dynamic.py b/lib/sqlalchemy/orm/dynamic.py index a4b5f58c7..ac7eba03b 100644 --- a/lib/sqlalchemy/orm/dynamic.py +++ b/lib/sqlalchemy/orm/dynamic.py @@ -24,6 +24,7 @@ from .query import Query from .. import exc from .. import log from .. import util +from ..engine import result @log.class_logger @@ -324,17 +325,28 @@ class AppenderMixin(object): session = property(session, lambda s, x: None) - def __iter__(self): + def _iter(self): sess = self.session if sess is None: - return iter( + state = attributes.instance_state(self.instance) + if state.detached: + util.warn( + "Instance %s is detached, dynamic relationship cannot " + "return a correct result. This warning will become " + "a DetachedInstanceError in a future release." + % (orm_util.state_str(state)) + ) + + return result.IteratorResult( + result.SimpleResultMetaData([self.attr.class_.__name__]), self.attr._get_collection_history( attributes.instance_state(self.instance), attributes.PASSIVE_NO_INITIALIZE, - ).added_items - ) + ).added_items, + _source_supports_scalars=True, + ).scalars() else: - return iter(self._generate(sess)) + return self._generate(sess)._iter() def __getitem__(self, index): sess = self.session diff --git a/test/orm/test_dynamic.py b/test/orm/test_dynamic.py index 8ea04c268..e87b5a363 100644 --- a/test/orm/test_dynamic.py +++ b/test/orm/test_dynamic.py @@ -233,6 +233,8 @@ class DynamicTest(_DynamicFixture, _fixtures.FixtureTest, AssertsCompiledSQL): ) def test_detached_raise(self): + """so filtering on a detached dynamic list raises an error...""" + User, Address = self._user_address_fixture() sess = fixture_session() u = sess.query(User).get(8) @@ -243,6 +245,35 @@ class DynamicTest(_DynamicFixture, _fixtures.FixtureTest, AssertsCompiledSQL): email_address="e", ) + def test_detached_all_empty_list(self): + """test #6426 - but you can call .all() on it and you get an empty + list. This is legacy stuff, as this should be raising + DetachedInstanceError. + + """ + + User, Address = self._user_address_fixture() + sess = fixture_session() + u = sess.query(User).get(8) + sess.expunge(u) + + with testing.expect_warnings( + r"Instance <User .*> is detached, dynamic relationship" + ): + eq_(u.addresses.all(), []) + + with testing.expect_warnings( + r"Instance <User .*> is detached, dynamic relationship" + ): + eq_(list(u.addresses), []) + + def test_transient_all_empty_list(self): + User, Address = self._user_address_fixture() + u1 = User() + eq_(u1.addresses.all(), []) + + eq_(list(u1.addresses), []) + def test_no_uselist_false(self): User, Address = self._user_address_fixture( addresses_args={"uselist": False} |
