]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
The ``noload`` loader option is now deprecated.
authorFederico Caselli <cfederico87@gmail.com>
Fri, 24 Jan 2025 22:00:06 +0000 (23:00 +0100)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 24 Feb 2025 19:25:11 +0000 (14:25 -0500)
Fixes: #11045
Change-Id: If77517926eda71f92cd92b2d22a69a5ee172274e

16 files changed:
doc/build/changelog/unreleased_21/11045.rst [new file with mode: 0644]
lib/sqlalchemy/orm/_orm_constructors.py
lib/sqlalchemy/orm/strategies.py
lib/sqlalchemy/orm/strategy_options.py
lib/sqlalchemy/testing/assertions.py
lib/sqlalchemy/util/langhelpers.py
test/orm/inheritance/test_assorted_poly.py
test/orm/test_ac_relationships.py
test/orm/test_default_strategies.py
test/orm/test_deprecations.py
test/orm/test_dynamic.py
test/orm/test_expire.py
test/orm/test_pickled.py
test/orm/test_relationships.py
test/orm/test_subquery_relations.py
test/orm/test_unitofwork.py

diff --git a/doc/build/changelog/unreleased_21/11045.rst b/doc/build/changelog/unreleased_21/11045.rst
new file mode 100644 (file)
index 0000000..8788d33
--- /dev/null
@@ -0,0 +1,8 @@
+.. change::
+    :tags: orm
+    :tickets: 11045
+
+    The :func:`_orm.noload` relationship loader option and related
+    ``lazy='noload'`` setting is deprecated and will be removed in a future
+    release.   This option was originally intended for custom loader patterns
+    that are no longer applicable in modern SQLAlchemy.
index 9e42a834fa3e297f883a5109b60f94b45865478c..b2acc93b43c02837531a88b1c771faacc47b5952 100644 (file)
@@ -1426,11 +1426,6 @@ def relationship(
         issues a JOIN to the immediate parent object, specifying primary
         key identifiers using an IN clause.
 
-      * ``noload`` - no loading should occur at any time.  The related
-        collection will remain empty.   The ``noload`` strategy is not
-        recommended for general use.  For a general use "never load"
-        approach, see :ref:`write_only_relationship`
-
       * ``raise`` - lazy loading is disallowed; accessing
         the attribute, if its value were not already loaded via eager
         loading, will raise an :exc:`~sqlalchemy.exc.InvalidRequestError`.
@@ -1493,6 +1488,13 @@ def relationship(
             :ref:`write_only_relationship` - more generally useful approach
             for large collections that should not fully load into memory
 
+      * ``noload`` - no loading should occur at any time.  The related
+        collection will remain empty.
+
+        .. deprecated:: 2.1 The ``noload`` loader strategy is deprecated and
+           will be removed in a future release.  This option produces incorrect
+           results by returning ``None`` for related items.
+
       * True - a synonym for 'select'
 
       * False - a synonym for 'joined'
index 8a530399dcc26f5de2b7c6669e44b2193197ccb5..8a5d1af961498166f304e5faf5c45aa45d58fdae 100644 (file)
@@ -638,6 +638,13 @@ class _NoLoader(_AbstractRelationshipLoader):
 
     __slots__ = ()
 
+    @util.deprecated(
+        "2.1",
+        "The ``noload`` loader strategy is deprecated and will be removed "
+        "in a future release.  This option "
+        "produces incorrect results by returning ``None`` for related "
+        "items.",
+    )
     def init_class_attribute(self, mapper):
         self.is_class_level = True
 
index 4ecbfd64c1e084c1a7d1f90beaa07bbbb6338053..5d21237198384ce4dfe69a1b6cf3e98c4c50f759 100644 (file)
@@ -478,6 +478,13 @@ class _AbstractLoad(traversals.GenerativeOnTraversal, LoaderOption):
         )
         return loader
 
+    @util.deprecated(
+        "2.1",
+        "The :func:`_orm.noload` option is deprecated and will be removed "
+        "in a future release.  This option "
+        "produces incorrect results by returning ``None`` for related "
+        "items.",
+    )
     def noload(self, attr: _AttrType) -> Self:
         """Indicate that the given relationship attribute should remain
         unloaded.
@@ -485,17 +492,9 @@ class _AbstractLoad(traversals.GenerativeOnTraversal, LoaderOption):
         The relationship attribute will return ``None`` when accessed without
         producing any loading effect.
 
-        This function is part of the :class:`_orm.Load` interface and supports
-        both method-chained and standalone operation.
-
         :func:`_orm.noload` applies to :func:`_orm.relationship` attributes
         only.
 
-        .. legacy:: The :func:`_orm.noload` option is **legacy**.  As it
-           forces collections to be empty, which invariably leads to
-           non-intuitive and difficult to predict results.  There are no
-           legitimate uses for this option in modern SQLAlchemy.
-
         .. seealso::
 
             :ref:`loading_toplevel`
index 8364c15f8ffc8cc5f0814daf7235f1526e8e89fb..effe50d4810952fcd2cb4c00fd7485b15ade4e1b 100644 (file)
@@ -89,6 +89,12 @@ def expect_deprecated(*messages, **kw):
     )
 
 
+def expect_noload_deprecation():
+    return expect_deprecated(
+        r"The (?:``noload`` loader strategy|noload\(\) option) is deprecated."
+    )
+
+
 def expect_deprecated_20(*messages, **kw):
     return _expect_warnings_sqla_only(
         sa_exc.Base20DeprecationWarning, messages, **kw
index 19c1cc21e38e1bb8970f6197527785ed2d6b7824..f7879d55c07820486f5af4b6172b1c86d74f80aa 100644 (file)
@@ -1846,6 +1846,10 @@ def _warnings_warn(
     category: Optional[Type[Warning]] = None,
     stacklevel: int = 2,
 ) -> None:
+
+    if category is None and isinstance(message, Warning):
+        category = type(message)
+
     # adjust the given stacklevel to be outside of SQLAlchemy
     try:
         frame = sys._getframe(stacklevel)
index ab06dbaea3d4dc07ce1ce8c1cdafa8faf30d2371..2b15b74251a4403466fcb9df738e1cb6520e6918 100644 (file)
@@ -41,6 +41,7 @@ from sqlalchemy.testing import config
 from sqlalchemy.testing import eq_
 from sqlalchemy.testing import expect_warnings
 from sqlalchemy.testing import fixtures
+from sqlalchemy.testing.assertions import expect_noload_deprecation
 from sqlalchemy.testing.entities import ComparableEntity
 from sqlalchemy.testing.fixtures import fixture_session
 from sqlalchemy.testing.provision import normalize_sequence
@@ -2355,7 +2356,8 @@ class CorrelateExceptWPolyAdaptTest(
     def test_poly_query_on_correlate(self):
         Common, Superclass = self._fixture(False)
 
-        poly = with_polymorphic(Superclass, "*")
+        with expect_noload_deprecation():
+            poly = with_polymorphic(Superclass, "*")
 
         s = fixture_session()
         q = (
@@ -2384,7 +2386,8 @@ class CorrelateExceptWPolyAdaptTest(
     def test_poly_query_on_correlate_except(self):
         Common, Superclass = self._fixture(True)
 
-        poly = with_polymorphic(Superclass, "*")
+        with expect_noload_deprecation():
+            poly = with_polymorphic(Superclass, "*")
 
         s = fixture_session()
         q = (
index 603e71d249ddde62b9011ec992cce4200c9b9864..34f4e37ee49c5b8804f23cc5b4c0db974c9344a0 100644 (file)
@@ -17,6 +17,7 @@ from sqlalchemy.orm import Session
 from sqlalchemy.testing import eq_
 from sqlalchemy.testing import expect_warnings
 from sqlalchemy.testing import fixtures
+from sqlalchemy.testing.assertions import expect_noload_deprecation
 from sqlalchemy.testing.assertions import expect_raises_message
 from sqlalchemy.testing.assertsql import CompiledSQL
 from sqlalchemy.testing.entities import ComparableEntity
@@ -142,7 +143,8 @@ class AliasedClassRelationshipTest(
                 for b in a1.partitioned_bs:
                     eq_(b.cs, [])
 
-        self.assert_sql_count(testing.db, go, 2)
+        with expect_noload_deprecation():
+            self.assert_sql_count(testing.db, go, 2)
 
     @testing.combinations("ac_attribute", "ac_attr_w_of_type")
     def test_selectinload_w_joinedload_after(self, calling_style):
index 178b03fe6f6f355b5dfde630b4754d822537ac59..c1989d1f69c65bda87ce828a344d99e453d86e0f 100644 (file)
@@ -18,7 +18,7 @@ from sqlalchemy.testing.fixtures import fixture_session
 from test.orm import _fixtures
 
 
-class DefaultStrategyOptionsTest(_fixtures.FixtureTest):
+class DefaultStrategyOptionsTestFixtures(_fixtures.FixtureTest):
     def _assert_fully_loaded(self, users):
         # verify everything loaded, with no additional sql needed
         def go():
@@ -193,6 +193,9 @@ class DefaultStrategyOptionsTest(_fixtures.FixtureTest):
 
         return fixture_session()
 
+
+class DefaultStrategyOptionsTest(DefaultStrategyOptionsTestFixtures):
+
     def test_downgrade_baseline(self):
         """Mapper strategy defaults load as expected
         (compare to rest of DefaultStrategyOptionsTest downgrade tests)."""
@@ -368,67 +371,6 @@ class DefaultStrategyOptionsTest(_fixtures.FixtureTest):
         # lastly, make sure they actually loaded properly
         eq_(users, self.static.user_all_result)
 
-    def test_noload_with_joinedload(self):
-        """Mapper load strategy defaults can be downgraded with
-        noload('*') option, while explicit joinedload() option
-        is still honored"""
-        sess = self._downgrade_fixture()
-        users = []
-
-        # test noload('*') shuts off 'orders' subquery, only 1 sql
-        def go():
-            users[:] = (
-                sess.query(self.classes.User)
-                .options(sa.orm.noload("*"))
-                .options(joinedload(self.classes.User.addresses))
-                .order_by(self.classes.User.id)
-                .all()
-            )
-
-        self.assert_sql_count(testing.db, go, 1)
-
-        # verify all the addresses were joined loaded (no more sql)
-        self._assert_addresses_loaded(users)
-
-        # User.orders should have loaded "noload" (meaning [])
-        def go():
-            for u in users:
-                assert u.orders == []
-
-        self.assert_sql_count(testing.db, go, 0)
-
-    def test_noload_with_subqueryload(self):
-        """Mapper load strategy defaults can be downgraded with
-        noload('*') option, while explicit subqueryload() option
-        is still honored"""
-        sess = self._downgrade_fixture()
-        users = []
-
-        # test noload('*') option combined with subqueryload()
-        # shuts off 'addresses' load AND orders.items load: 2 sql expected
-        def go():
-            users[:] = (
-                sess.query(self.classes.User)
-                .options(sa.orm.noload("*"))
-                .options(subqueryload(self.classes.User.orders))
-                .order_by(self.classes.User.id)
-                .all()
-            )
-
-        self.assert_sql_count(testing.db, go, 2)
-
-        def go():
-            # Verify orders have already been loaded: 0 sql
-            for u, static in zip(users, self.static.user_all_result):
-                assert len(u.orders) == len(static.orders)
-            # Verify noload('*') prevented orders.items load
-            # and set 'items' to []
-            for u in users:
-                for o in u.orders:
-                    assert o.items == []
-
-        self.assert_sql_count(testing.db, go, 0)
-
     def test_joined(self):
         """Mapper load strategy defaults can be upgraded with
         joinedload('*') option."""
@@ -654,99 +596,6 @@ class DefaultStrategyOptionsTest(_fixtures.FixtureTest):
         self._assert_fully_loaded(users)
 
 
-class NoLoadTest(_fixtures.FixtureTest):
-    run_inserts = "once"
-    run_deletes = None
-
-    def test_o2m_noload(self):
-        Address, addresses, users, User = (
-            self.classes.Address,
-            self.tables.addresses,
-            self.tables.users,
-            self.classes.User,
-        )
-
-        m = self.mapper_registry.map_imperatively(
-            User,
-            users,
-            properties=dict(
-                addresses=relationship(
-                    self.mapper_registry.map_imperatively(Address, addresses),
-                    lazy="noload",
-                )
-            ),
-        )
-        q = fixture_session().query(m)
-        result = [None]
-
-        def go():
-            x = q.filter(User.id == 7).all()
-            x[0].addresses
-            result[0] = x
-
-        self.assert_sql_count(testing.db, go, 1)
-
-        self.assert_result(
-            result[0], User, {"id": 7, "addresses": (Address, [])}
-        )
-
-    def test_upgrade_o2m_noload_lazyload_option(self):
-        Address, addresses, users, User = (
-            self.classes.Address,
-            self.tables.addresses,
-            self.tables.users,
-            self.classes.User,
-        )
-
-        m = self.mapper_registry.map_imperatively(
-            User,
-            users,
-            properties=dict(
-                addresses=relationship(
-                    self.mapper_registry.map_imperatively(Address, addresses),
-                    lazy="noload",
-                )
-            ),
-        )
-        q = fixture_session().query(m).options(sa.orm.lazyload(User.addresses))
-        result = [None]
-
-        def go():
-            x = q.filter(User.id == 7).all()
-            x[0].addresses
-            result[0] = x
-
-        self.sql_count_(2, go)
-
-        self.assert_result(
-            result[0], User, {"id": 7, "addresses": (Address, [{"id": 1}])}
-        )
-
-    def test_m2o_noload_option(self):
-        Address, addresses, users, User = (
-            self.classes.Address,
-            self.tables.addresses,
-            self.tables.users,
-            self.classes.User,
-        )
-        self.mapper_registry.map_imperatively(
-            Address, addresses, properties={"user": relationship(User)}
-        )
-        self.mapper_registry.map_imperatively(User, users)
-        s = fixture_session()
-        a1 = (
-            s.query(Address)
-            .filter_by(id=1)
-            .options(sa.orm.noload(Address.user))
-            .first()
-        )
-
-        def go():
-            eq_(a1.user, None)
-
-        self.sql_count_(0, go)
-
-
 class Issue11292Test(fixtures.DeclarativeMappedTest):
     @classmethod
     def setup_classes(cls):
index b99bc643a1861d7a001f59570c22652c746036c8..fa04a19d3e195ffa7b5a45ca3c3d7d749d04682f 100644 (file)
@@ -40,12 +40,15 @@ from sqlalchemy.orm import relationship
 from sqlalchemy.orm import scoped_session
 from sqlalchemy.orm import Session
 from sqlalchemy.orm import sessionmaker
+from sqlalchemy.orm import strategies
 from sqlalchemy.orm import subqueryload
 from sqlalchemy.orm import synonym
 from sqlalchemy.orm import undefer
 from sqlalchemy.orm import with_parent
 from sqlalchemy.orm import with_polymorphic
 from sqlalchemy.orm.collections import collection
+from sqlalchemy.orm.strategy_options import lazyload
+from sqlalchemy.orm.strategy_options import noload
 from sqlalchemy.testing import assert_raises_message
 from sqlalchemy.testing import assertions
 from sqlalchemy.testing import AssertsCompiledSQL
@@ -56,6 +59,8 @@ from sqlalchemy.testing import expect_raises_message
 from sqlalchemy.testing import fixtures
 from sqlalchemy.testing import is_
 from sqlalchemy.testing import mock
+from sqlalchemy.testing.assertions import expect_noload_deprecation
+from sqlalchemy.testing.assertions import in_
 from sqlalchemy.testing.entities import ComparableEntity
 from sqlalchemy.testing.fixtures import CacheKeyFixture
 from sqlalchemy.testing.fixtures import fixture_session
@@ -65,18 +70,15 @@ from . import _fixtures
 from .inheritance import _poly_fixtures
 from .inheritance._poly_fixtures import Manager
 from .inheritance._poly_fixtures import Person
+from .test_default_strategies import DefaultStrategyOptionsTestFixtures
 from .test_deferred import InheritanceTest as _deferred_InheritanceTest
+from .test_dynamic import _DynamicFixture
+from .test_dynamic import _WriteOnlyFixture
 from .test_options import PathTest as OptionsPathTest
 from .test_options import PathTest
 from .test_options import QueryTest as OptionsQueryTest
 from .test_query import QueryTest
 
-if True:
-    # hack - zimports won't stop reformatting this to be too-long for now
-    from .test_default_strategies import (
-        DefaultStrategyOptionsTest as _DefaultStrategyOptionsTest,
-    )
-
 join_aliased_dep = (
     r"The ``aliased`` and ``from_joinpoint`` keyword arguments to "
     r"Query.join\(\)"
@@ -2732,7 +2734,7 @@ class MergeResultTest(_fixtures.FixtureTest):
         )
 
 
-class DefaultStrategyOptionsTest(_DefaultStrategyOptionsTest):
+class DefaultStrategyOptionsTest(DefaultStrategyOptionsTestFixtures):
     def test_joined_path_wildcards(self):
         sess = self._upgrade_fixture()
         users = []
@@ -2787,6 +2789,69 @@ class DefaultStrategyOptionsTest(_DefaultStrategyOptionsTest):
             # verify everything loaded, with no additional sql needed
             self._assert_fully_loaded(users)
 
+    def test_noload_with_joinedload(self):
+        """Mapper load strategy defaults can be downgraded with
+        noload('*') option, while explicit joinedload() option
+        is still honored"""
+        sess = self._downgrade_fixture()
+        users = []
+
+        # test noload('*') shuts off 'orders' subquery, only 1 sql
+        def go():
+            users[:] = (
+                sess.query(self.classes.User)
+                .options(sa.orm.noload("*"))
+                .options(joinedload(self.classes.User.addresses))
+                .order_by(self.classes.User.id)
+                .all()
+            )
+
+        with expect_noload_deprecation():
+            self.assert_sql_count(testing.db, go, 1)
+
+        # verify all the addresses were joined loaded (no more sql)
+        self._assert_addresses_loaded(users)
+
+        # User.orders should have loaded "noload" (meaning [])
+        def go():
+            for u in users:
+                assert u.orders == []
+
+        self.assert_sql_count(testing.db, go, 0)
+
+    def test_noload_with_subqueryload(self):
+        """Mapper load strategy defaults can be downgraded with
+        noload('*') option, while explicit subqueryload() option
+        is still honored"""
+        sess = self._downgrade_fixture()
+        users = []
+
+        # test noload('*') option combined with subqueryload()
+        # shuts off 'addresses' load AND orders.items load: 2 sql expected
+        def go():
+            users[:] = (
+                sess.query(self.classes.User)
+                .options(sa.orm.noload("*"))
+                .options(subqueryload(self.classes.User.orders))
+                .order_by(self.classes.User.id)
+                .all()
+            )
+
+        with expect_noload_deprecation():
+            self.assert_sql_count(testing.db, go, 2)
+
+        def go():
+            # Verify orders have already been loaded: 0 sql
+            for u, static in zip(users, self.static.user_all_result):
+                assert len(u.orders) == len(static.orders)
+            # Verify noload('*') prevented orders.items load
+            # and set 'items' to []
+            for u in users:
+                for o in u.orders:
+                    assert o.items == []
+
+        self.assert_sql_count(testing.db, go, 0)
+
 
 class Deferred_InheritanceTest(_deferred_InheritanceTest):
     def test_defer_on_wildcard_subclass(self):
@@ -2812,3 +2877,326 @@ class Deferred_InheritanceTest(_deferred_InheritanceTest):
         )
         # note this doesn't apply to "bound" loaders since they don't seem
         # to have this ".*" feature.
+
+
+class NoLoadTest(_fixtures.FixtureTest):
+    run_inserts = "once"
+    run_deletes = None
+
+    def test_o2m_noload(self):
+        Address, addresses, users, User = (
+            self.classes.Address,
+            self.tables.addresses,
+            self.tables.users,
+            self.classes.User,
+        )
+
+        m = self.mapper_registry.map_imperatively(
+            User,
+            users,
+            properties=dict(
+                addresses=relationship(
+                    self.mapper_registry.map_imperatively(Address, addresses),
+                    lazy="noload",
+                )
+            ),
+        )
+        q = fixture_session().query(m)
+        result = [None]
+
+        def go():
+            x = q.filter(User.id == 7).all()
+            x[0].addresses
+            result[0] = x
+
+        with expect_noload_deprecation():
+            self.assert_sql_count(testing.db, go, 1)
+
+        self.assert_result(
+            result[0], User, {"id": 7, "addresses": (Address, [])}
+        )
+
+    def test_upgrade_o2m_noload_lazyload_option(self):
+        Address, addresses, users, User = (
+            self.classes.Address,
+            self.tables.addresses,
+            self.tables.users,
+            self.classes.User,
+        )
+
+        m = self.mapper_registry.map_imperatively(
+            User,
+            users,
+            properties=dict(
+                addresses=relationship(
+                    self.mapper_registry.map_imperatively(Address, addresses),
+                    lazy="noload",
+                )
+            ),
+        )
+        with expect_noload_deprecation():
+            q = (
+                fixture_session()
+                .query(m)
+                .options(sa.orm.lazyload(User.addresses))
+            )
+        result = [None]
+
+        def go():
+            x = q.filter(User.id == 7).all()
+            x[0].addresses
+            result[0] = x
+
+        self.sql_count_(2, go)
+
+        self.assert_result(
+            result[0], User, {"id": 7, "addresses": (Address, [{"id": 1}])}
+        )
+
+    def test_m2o_noload_option(self):
+        Address, addresses, users, User = (
+            self.classes.Address,
+            self.tables.addresses,
+            self.tables.users,
+            self.classes.User,
+        )
+        self.mapper_registry.map_imperatively(
+            Address, addresses, properties={"user": relationship(User)}
+        )
+        self.mapper_registry.map_imperatively(User, users)
+        s = fixture_session()
+        with expect_noload_deprecation():
+            a1 = (
+                s.query(Address)
+                .filter_by(id=1)
+                .options(sa.orm.noload(Address.user))
+                .first()
+            )
+
+        def go():
+            eq_(a1.user, None)
+
+        self.sql_count_(0, go)
+
+
+class DynamicTest(_DynamicFixture, _fixtures.FixtureTest):
+
+    @testing.combinations(("star",), ("attronly",), argnames="type_")
+    def test_noload_issue(self, type_, user_address_fixture):
+        """test #6420.   a noload that hits the dynamic loader
+        should have no effect.
+
+        """
+
+        User, Address = user_address_fixture()
+
+        s = fixture_session()
+
+        with expect_noload_deprecation():
+
+            if type_ == "star":
+                u1 = s.query(User).filter_by(id=7).options(noload("*")).first()
+                assert "name" not in u1.__dict__["name"]
+            elif type_ == "attronly":
+                u1 = (
+                    s.query(User)
+                    .filter_by(id=7)
+                    .options(noload(User.addresses))
+                    .first()
+                )
+
+                eq_(u1.__dict__["name"], "jack")
+
+        # noload doesn't affect a dynamic loader, because it has no state
+        eq_(list(u1.addresses), [Address(id=1)])
+
+
+class WriteOnlyTest(_WriteOnlyFixture, _fixtures.FixtureTest):
+
+    @testing.combinations(("star",), ("attronly",), argnames="type_")
+    def test_noload_issue(self, type_, user_address_fixture):
+        """test #6420.   a noload that hits the dynamic loader
+        should have no effect.
+
+        """
+
+        User, Address = user_address_fixture()
+
+        s = fixture_session()
+
+        with expect_noload_deprecation():
+
+            if type_ == "star":
+                u1 = s.query(User).filter_by(id=7).options(noload("*")).first()
+                assert "name" not in u1.__dict__["name"]
+            elif type_ == "attronly":
+                u1 = (
+                    s.query(User)
+                    .filter_by(id=7)
+                    .options(noload(User.addresses))
+                    .first()
+                )
+
+                eq_(u1.__dict__["name"], "jack")
+
+
+class ExpireTest(_fixtures.FixtureTest):
+    def test_state_noload_to_lazy(self):
+        """Behavioral test to verify the current activity of
+        loader callables
+
+        """
+
+        users, Address, addresses, User = (
+            self.tables.users,
+            self.classes.Address,
+            self.tables.addresses,
+            self.classes.User,
+        )
+
+        self.mapper_registry.map_imperatively(
+            User,
+            users,
+            properties={"addresses": relationship(Address, lazy="noload")},
+        )
+        self.mapper_registry.map_imperatively(Address, addresses)
+
+        sess = fixture_session(autoflush=False)
+        with expect_noload_deprecation():
+            u1 = sess.query(User).options(lazyload(User.addresses)).first()
+        assert isinstance(
+            attributes.instance_state(u1).callables["addresses"],
+            strategies._LoadLazyAttribute,
+        )
+        # expire, it goes away from callables as of 1.4 and is considered
+        # to be expired
+        sess.expire(u1)
+
+        assert "addresses" in attributes.instance_state(u1).expired_attributes
+        assert "addresses" not in attributes.instance_state(u1).callables
+
+        # load it
+        sess.query(User).first()
+        assert (
+            "addresses" not in attributes.instance_state(u1).expired_attributes
+        )
+        assert "addresses" not in attributes.instance_state(u1).callables
+
+        sess.expunge_all()
+        u1 = sess.query(User).options(lazyload(User.addresses)).first()
+        sess.expire(u1, ["addresses"])
+        assert (
+            "addresses" not in attributes.instance_state(u1).expired_attributes
+        )
+        assert isinstance(
+            attributes.instance_state(u1).callables["addresses"],
+            strategies._LoadLazyAttribute,
+        )
+
+        # load the attr, goes away
+        u1.addresses
+        assert (
+            "addresses" not in attributes.instance_state(u1).expired_attributes
+        )
+        assert "addresses" not in attributes.instance_state(u1).callables
+
+
+class NoLoadBackPopulates(_fixtures.FixtureTest):
+    """test the noload stratgegy which unlike others doesn't use
+    lazyloader to set up instrumentation"""
+
+    def test_o2m(self):
+        users, Address, addresses, User = (
+            self.tables.users,
+            self.classes.Address,
+            self.tables.addresses,
+            self.classes.User,
+        )
+
+        self.mapper_registry.map_imperatively(
+            User,
+            users,
+            properties={
+                "addresses": relationship(
+                    Address, back_populates="user", lazy="noload"
+                )
+            },
+        )
+
+        self.mapper_registry.map_imperatively(
+            Address, addresses, properties={"user": relationship(User)}
+        )
+        with expect_noload_deprecation():
+            u1 = User()
+        a1 = Address()
+        u1.addresses.append(a1)
+        is_(a1.user, u1)
+
+    def test_m2o(self):
+        users, Address, addresses, User = (
+            self.tables.users,
+            self.classes.Address,
+            self.tables.addresses,
+            self.classes.User,
+        )
+
+        self.mapper_registry.map_imperatively(
+            User, users, properties={"addresses": relationship(Address)}
+        )
+
+        self.mapper_registry.map_imperatively(
+            Address,
+            addresses,
+            properties={
+                "user": relationship(
+                    User, back_populates="addresses", lazy="noload"
+                )
+            },
+        )
+        with expect_noload_deprecation():
+            u1 = User()
+        a1 = Address()
+        a1.user = u1
+        in_(a1, u1.addresses)
+
+
+class ManyToOneTest(_fixtures.FixtureTest):
+    run_inserts = None
+
+    def test_bidirectional_no_load(self):
+        users, Address, addresses, User = (
+            self.tables.users,
+            self.classes.Address,
+            self.tables.addresses,
+            self.classes.User,
+        )
+
+        self.mapper_registry.map_imperatively(
+            User,
+            users,
+            properties={
+                "addresses": relationship(
+                    Address, backref="user", lazy="noload"
+                )
+            },
+        )
+        self.mapper_registry.map_imperatively(Address, addresses)
+
+        # try it on unsaved objects
+        with expect_noload_deprecation():
+            u1 = User(name="u1")
+        a1 = Address(email_address="e1")
+        a1.user = u1
+
+        session = fixture_session()
+        session.add(u1)
+        session.flush()
+        session.expunge_all()
+
+        a1 = session.get(Address, a1.id)
+
+        a1.user = None
+        session.flush()
+        session.expunge_all()
+        assert session.get(Address, a1.id).user is None
+        assert session.get(User, u1.id).addresses == []
index 465e29929e9e8c8b24a313dc0d1a8e95b3295f1d..9378f1ef50c909480507408a13b1addf3240bb7e 100644 (file)
@@ -16,7 +16,6 @@ from sqlalchemy.orm import configure_mappers
 from sqlalchemy.orm import exc as orm_exc
 from sqlalchemy.orm import Mapped
 from sqlalchemy.orm import mapped_column
-from sqlalchemy.orm import noload
 from sqlalchemy.orm import PassiveFlag
 from sqlalchemy.orm import Query
 from sqlalchemy.orm import relationship
@@ -548,33 +547,6 @@ class DynamicTest(_DynamicFixture, _fixtures.FixtureTest, AssertsCompiledSQL):
             [],
         )
 
-    @testing.combinations(("star",), ("attronly",), argnames="type_")
-    def test_noload_issue(self, type_, user_address_fixture):
-        """test #6420.   a noload that hits the dynamic loader
-        should have no effect.
-
-        """
-
-        User, Address = user_address_fixture()
-
-        s = fixture_session()
-
-        if type_ == "star":
-            u1 = s.query(User).filter_by(id=7).options(noload("*")).first()
-            assert "name" not in u1.__dict__["name"]
-        elif type_ == "attronly":
-            u1 = (
-                s.query(User)
-                .filter_by(id=7)
-                .options(noload(User.addresses))
-                .first()
-            )
-
-            eq_(u1.__dict__["name"], "jack")
-
-        # noload doesn't affect a dynamic loader, because it has no state
-        eq_(list(u1.addresses), [Address(id=1)])
-
     def test_m2m(self, order_item_fixture):
         Order, Item = order_item_fixture(
             items_args={"backref": backref("orders", lazy="dynamic")}
@@ -799,30 +771,6 @@ class WriteOnlyTest(
 ):
     __dialect__ = "default"
 
-    @testing.combinations(("star",), ("attronly",), argnames="type_")
-    def test_noload_issue(self, type_, user_address_fixture):
-        """test #6420.   a noload that hits the dynamic loader
-        should have no effect.
-
-        """
-
-        User, Address = user_address_fixture()
-
-        s = fixture_session()
-
-        if type_ == "star":
-            u1 = s.query(User).filter_by(id=7).options(noload("*")).first()
-            assert "name" not in u1.__dict__["name"]
-        elif type_ == "attronly":
-            u1 = (
-                s.query(User)
-                .filter_by(id=7)
-                .options(noload(User.addresses))
-                .first()
-            )
-
-            eq_(u1.__dict__["name"], "jack")
-
     def test_iteration_error(self, user_address_fixture):
         User, Address = user_address_fixture()
 
index 2b15c2443c222b337b5f770d943d06b0aef19e0c..84511cb2263c3c2625c9d257121d75f7d6d98d63 100644 (file)
@@ -1664,64 +1664,6 @@ class ExpireTest(_fixtures.FixtureTest):
         assert "name" in attributes.instance_state(u1).expired_attributes
         assert "name" not in attributes.instance_state(u1).callables
 
-    def test_state_noload_to_lazy(self):
-        """Behavioral test to verify the current activity of
-        loader callables
-
-        """
-
-        users, Address, addresses, User = (
-            self.tables.users,
-            self.classes.Address,
-            self.tables.addresses,
-            self.classes.User,
-        )
-
-        self.mapper_registry.map_imperatively(
-            User,
-            users,
-            properties={"addresses": relationship(Address, lazy="noload")},
-        )
-        self.mapper_registry.map_imperatively(Address, addresses)
-
-        sess = fixture_session(autoflush=False)
-        u1 = sess.query(User).options(lazyload(User.addresses)).first()
-        assert isinstance(
-            attributes.instance_state(u1).callables["addresses"],
-            strategies._LoadLazyAttribute,
-        )
-        # expire, it goes away from callables as of 1.4 and is considered
-        # to be expired
-        sess.expire(u1)
-
-        assert "addresses" in attributes.instance_state(u1).expired_attributes
-        assert "addresses" not in attributes.instance_state(u1).callables
-
-        # load it
-        sess.query(User).first()
-        assert (
-            "addresses" not in attributes.instance_state(u1).expired_attributes
-        )
-        assert "addresses" not in attributes.instance_state(u1).callables
-
-        sess.expunge_all()
-        u1 = sess.query(User).options(lazyload(User.addresses)).first()
-        sess.expire(u1, ["addresses"])
-        assert (
-            "addresses" not in attributes.instance_state(u1).expired_attributes
-        )
-        assert isinstance(
-            attributes.instance_state(u1).callables["addresses"],
-            strategies._LoadLazyAttribute,
-        )
-
-        # load the attr, goes away
-        u1.addresses
-        assert (
-            "addresses" not in attributes.instance_state(u1).expired_attributes
-        )
-        assert "addresses" not in attributes.instance_state(u1).callables
-
     def test_deferred_expire_w_transient_to_detached(self):
         orders, Order = self.tables.orders, self.classes.Order
         self.mapper_registry.map_imperatively(
index 18904cc38611f7b65d6fcda27f633fee374fa4af..0c69b2cc861e87a40606f89764726610336bd132 100644 (file)
@@ -239,7 +239,7 @@ class PickleTest(fixtures.MappedTest):
         self.mapper_registry.map_imperatively(
             User,
             users,
-            properties={"addresses": relationship(Address, lazy="noload")},
+            properties={"addresses": relationship(Address, lazy="raise")},
         )
         self.mapper_registry.map_imperatively(Address, addresses)
 
@@ -305,7 +305,7 @@ class PickleTest(fixtures.MappedTest):
         self.mapper_registry.map_imperatively(
             User,
             users,
-            properties={"addresses": relationship(Address, lazy="noload")},
+            properties={"addresses": relationship(Address)},
         )
         self.mapper_registry.map_imperatively(Address, addresses)
 
@@ -321,7 +321,7 @@ class PickleTest(fixtures.MappedTest):
         self.mapper_registry.map_imperatively(
             User,
             users,
-            properties={"addresses": relationship(Address, lazy="noload")},
+            properties={"addresses": relationship(Address)},
         )
         self.mapper_registry.map_imperatively(Address, addresses)
 
index 0d4211656a30cb4b023be86c8df7db9423302199..589dcf2fed46b3eae7e3b9e0a9be915d314d1686 100644 (file)
@@ -38,7 +38,6 @@ from sqlalchemy.testing import eq_
 from sqlalchemy.testing import expect_raises_message
 from sqlalchemy.testing import expect_warnings
 from sqlalchemy.testing import fixtures
-from sqlalchemy.testing import in_
 from sqlalchemy.testing import is_
 from sqlalchemy.testing.assertsql import assert_engine
 from sqlalchemy.testing.assertsql import CompiledSQL
@@ -2478,65 +2477,6 @@ class ManualBackrefTest(_fixtures.FixtureTest):
         )
 
 
-class NoLoadBackPopulates(_fixtures.FixtureTest):
-    """test the noload stratgegy which unlike others doesn't use
-    lazyloader to set up instrumentation"""
-
-    def test_o2m(self):
-        users, Address, addresses, User = (
-            self.tables.users,
-            self.classes.Address,
-            self.tables.addresses,
-            self.classes.User,
-        )
-
-        self.mapper_registry.map_imperatively(
-            User,
-            users,
-            properties={
-                "addresses": relationship(
-                    Address, back_populates="user", lazy="noload"
-                )
-            },
-        )
-
-        self.mapper_registry.map_imperatively(
-            Address, addresses, properties={"user": relationship(User)}
-        )
-
-        u1 = User()
-        a1 = Address()
-        u1.addresses.append(a1)
-        is_(a1.user, u1)
-
-    def test_m2o(self):
-        users, Address, addresses, User = (
-            self.tables.users,
-            self.classes.Address,
-            self.tables.addresses,
-            self.classes.User,
-        )
-
-        self.mapper_registry.map_imperatively(
-            User, users, properties={"addresses": relationship(Address)}
-        )
-
-        self.mapper_registry.map_imperatively(
-            Address,
-            addresses,
-            properties={
-                "user": relationship(
-                    User, back_populates="addresses", lazy="noload"
-                )
-            },
-        )
-
-        u1 = User()
-        a1 = Address()
-        a1.user = u1
-        in_(a1, u1.addresses)
-
-
 class JoinConditionErrorTest(fixtures.TestBase):
     def test_clauseelement_pj(self, registry):
         Base = registry.generate_base()
index 538c77c0cee338c1220b7c4e803a6a6f7cad4154..4b839d8efca46d721ac20d244a0d21e7d45f3aac 100644 (file)
@@ -3222,7 +3222,7 @@ class JoinedNoLoadConflictTest(fixtures.DeclarativeMappedTest):
             name = Column(String(20))
 
             children = relationship(
-                "Child", back_populates="parent", lazy="noload"
+                "Child", back_populates="parent", lazy="raise"
             )
 
         class Child(ComparableEntity, Base):
index 7b29b4362a092ea86b0d76755c7bddf989d63851..eb290156b816b7f93daa51b227c129c85b3343d0 100644 (file)
@@ -2463,43 +2463,6 @@ class ManyToOneTest(_fixtures.FixtureTest):
         u2 = session.get(User, u2.id)
         assert a1.user is u2
 
-    def test_bidirectional_no_load(self):
-        users, Address, addresses, User = (
-            self.tables.users,
-            self.classes.Address,
-            self.tables.addresses,
-            self.classes.User,
-        )
-
-        self.mapper_registry.map_imperatively(
-            User,
-            users,
-            properties={
-                "addresses": relationship(
-                    Address, backref="user", lazy="noload"
-                )
-            },
-        )
-        self.mapper_registry.map_imperatively(Address, addresses)
-
-        # try it on unsaved objects
-        u1 = User(name="u1")
-        a1 = Address(email_address="e1")
-        a1.user = u1
-
-        session = fixture_session()
-        session.add(u1)
-        session.flush()
-        session.expunge_all()
-
-        a1 = session.get(Address, a1.id)
-
-        a1.user = None
-        session.flush()
-        session.expunge_all()
-        assert session.get(Address, a1.id).user is None
-        assert session.get(User, u1.id).addresses == []
-
 
 class ManyToManyTest(_fixtures.FixtureTest):
     run_inserts = None