]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
add raiseload to load_only()
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 19 Sep 2022 16:18:47 +0000 (12:18 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 19 Sep 2022 22:27:28 +0000 (18:27 -0400)
currently this can't be finessed in another way,
at least not easily.   The deferred() that it sets up
doesn't seem to be cancellable.  in any case, this is more consistent
API with that of defer().

Change-Id: I30fca1cc371c9102d013cda8e23a522eea1033f8

doc/build/changelog/unreleased_20/load_only_raiseload.rst [new file with mode: 0644]
lib/sqlalchemy/orm/strategy_options.py
test/orm/test_deferred.py

diff --git a/doc/build/changelog/unreleased_20/load_only_raiseload.rst b/doc/build/changelog/unreleased_20/load_only_raiseload.rst
new file mode 100644 (file)
index 0000000..6d5716f
--- /dev/null
@@ -0,0 +1,7 @@
+.. change::
+    :tags: usecase, orm
+
+    Added :paramref:`_orm.load_only.raiseload` parameter to the
+    :func:`_orm.load_only` loader option, so that the unloaded attributes may
+    have "raise" behavior rather than lazy loading. Previously there wasn't
+    really a way to do this with the :func:`_orm.load_only` option directly.
index 2aed60b61cb650253b3681fc465a30be38da9d5a..2748556fd2be149707ba32de55fc5e1ed711570d 100644 (file)
@@ -165,9 +165,9 @@ class _AbstractLoad(traversals.GenerativeOnTraversal, LoaderOption):
         return cloned
 
     def load_only(
-        self: Self_AbstractLoad, *attrs: _AttrType
+        self: Self_AbstractLoad, *attrs: _AttrType, raiseload: bool = False
     ) -> Self_AbstractLoad:
-        """Indicate that for a particular entity, only the given list
+        r"""Indicate that for a particular entity, only the given list
         of column-based attribute names should be loaded; all others will be
         deferred.
 
@@ -200,14 +200,27 @@ class _AbstractLoad(traversals.GenerativeOnTraversal, LoaderOption):
             if the column property is defined with ``deferred=True``
             for the :func:`.column_property` function.
 
+        :param \*attrs: Attributes to be loaded, all others will be deferred.
+
+        :param raiseload: raise :class:`.InvalidRequestError` rather than
+         lazy loading a value when a deferred attribute is accessed. Used
+         to prevent unwanted SQL from being emitted.
+
+         .. versionadded:: 2.0
+
         """
         cloned = self._set_column_strategy(
             attrs,
             {"deferred": False, "instrument": True},
         )
+
+        wildcard_strategy = {"deferred": True, "instrument": True}
+        if raiseload:
+            wildcard_strategy["raiseload"] = True
+
         cloned = cloned._set_column_strategy(
             ("*",),
-            {"deferred": True, "instrument": True},
+            wildcard_strategy,
         )
         return cloned
 
@@ -612,9 +625,9 @@ class _AbstractLoad(traversals.GenerativeOnTraversal, LoaderOption):
 
         :param key: Attribute to be deferred.
 
-        :param raiseload: raise :class:`.InvalidRequestError` if the column
-         value is to be loaded from emitting SQL.   Used to prevent unwanted
-         SQL from being emitted.
+        :param raiseload: raise :class:`.InvalidRequestError` rather than
+         lazy loading a value when the deferred attribute is accessed. Used
+         to prevent unwanted SQL from being emitted.
 
         .. versionadded:: 1.4
 
@@ -2397,11 +2410,11 @@ def contains_eager(*keys: _AttrType, **kw: Any) -> _AbstractLoad:
 
 
 @loader_unbound_fn
-def load_only(*attrs: _AttrType) -> _AbstractLoad:
+def load_only(*attrs: _AttrType, raiseload: bool = False) -> _AbstractLoad:
     # TODO: attrs against different classes.  we likely have to
     # add some extra state to Load of some kind
     _, lead_element, _ = _parse_attr_argument(attrs[0])
-    return Load(lead_element).load_only(*attrs)
+    return Load(lead_element).load_only(*attrs, raiseload=raiseload)
 
 
 @loader_unbound_fn
@@ -2453,7 +2466,9 @@ def defaultload(*keys: _AttrType) -> _AbstractLoad:
 
 
 @loader_unbound_fn
-def defer(key: _AttrType, *addl_attrs: _AttrType, **kw: Any) -> _AbstractLoad:
+def defer(
+    key: _AttrType, *addl_attrs: _AttrType, raiseload: bool = False
+) -> _AbstractLoad:
     if addl_attrs:
         util.warn_deprecated(
             "The *addl_attrs on orm.defer is deprecated.  Please use "
@@ -2461,6 +2476,12 @@ def defer(key: _AttrType, *addl_attrs: _AttrType, **kw: Any) -> _AbstractLoad:
             "indicate a path.",
             version="1.3",
         )
+
+    if raiseload:
+        kw = {"raiseload": raiseload}
+    else:
+        kw = {}
+
     return _generate_from_keys(Load.defer, (key,) + addl_attrs, False, kw)
 
 
index 277ebfd882754a76c07f27ae4d6c3cf86dd2a85f..e20ce6324eb0627fc1361e009d3264dc203724fc 100644 (file)
@@ -2317,6 +2317,19 @@ class RaiseLoadTest(fixtures.DeclarativeMappedTest):
             "x",
         )
 
+    def test_load_only_raise_option_raise_column_plain(self):
+        A = self.classes.A
+        s = fixture_session()
+
+        a1 = s.query(A).options(load_only(A.y, A.z, raiseload=True)).first()
+        assert_raises_message(
+            sa.exc.InvalidRequestError,
+            "'A.x' is not available due to raiseload=True",
+            getattr,
+            a1,
+            "x",
+        )
+
     def test_deferred_raise_option_load_column_unexpire(self):
         A = self.classes.A
         s = fixture_session()