From: Mike Bayer Date: Mon, 19 Sep 2022 16:18:47 +0000 (-0400) Subject: add raiseload to load_only() X-Git-Tag: rel_2_0_0b1~53^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9020e104ebc7f4a0a3a44395c558fb0e119c32ee;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git add raiseload to load_only() 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 --- 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 index 0000000000..6d5716f4f5 --- /dev/null +++ b/doc/build/changelog/unreleased_20/load_only_raiseload.rst @@ -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. diff --git a/lib/sqlalchemy/orm/strategy_options.py b/lib/sqlalchemy/orm/strategy_options.py index 2aed60b61c..2748556fd2 100644 --- a/lib/sqlalchemy/orm/strategy_options.py +++ b/lib/sqlalchemy/orm/strategy_options.py @@ -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) diff --git a/test/orm/test_deferred.py b/test/orm/test_deferred.py index 277ebfd882..e20ce6324e 100644 --- a/test/orm/test_deferred.py +++ b/test/orm/test_deferred.py @@ -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()