]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
user_defined_options returns memoized options
authorFederico Caselli <cfederico87@gmail.com>
Tue, 19 May 2026 21:39:49 +0000 (23:39 +0200)
committerFederico Caselli <cfederico87@gmail.com>
Thu, 21 May 2026 21:14:17 +0000 (23:14 +0200)
Updated the attribute :attr:`_orm.ORMExecuteState.user_defined_options` to
include options that were added to the statement before calling
:meth:`.Select.with_only_columns` or :meth:`_orm.Query.with_entities`.

Fixes: #13309
Change-Id: Ie6e3f46662542010f4d524820ae697638f36d459

doc/build/changelog/unreleased_21/13309.rst [new file with mode: 0644]
lib/sqlalchemy/orm/session.py
test/orm/test_events.py

diff --git a/doc/build/changelog/unreleased_21/13309.rst b/doc/build/changelog/unreleased_21/13309.rst
new file mode 100644 (file)
index 0000000..a976861
--- /dev/null
@@ -0,0 +1,7 @@
+.. change::
+    :tags: orm, usecase
+    :tickets: 13309
+
+    Updated the attribute :attr:`_orm.ORMExecuteState.user_defined_options` to
+    include options that were added to the statement before calling
+    :meth:`.Select.with_only_columns` or :meth:`_orm.Query.with_entities`.
index cb37588ede0c6f8f97bb805ee06729894e40f26b..d03c90047ab2b1dfec51e43a3f0aaf1be7c2dacb 100644 (file)
@@ -809,10 +809,20 @@ class ORMExecuteState(util.MemoizedSlots):
         """The sequence of :class:`.UserDefinedOptions` that have been
         associated with the statement being invoked.
 
+        .. versionchanged:: 2.1 - the returned option take into
+            consideration any options added before calling
+            :meth:`_sql.Select.with_only_columns` or
+            :meth:`_orm.Query.with_entities`.
+
         """
+        items = [
+            self.statement,
+            *getattr(self.statement, "_memoized_select_entities", ()),
+        ]
         return [
             opt
-            for opt in self.statement._with_options
+            for item in items
+            for opt in item._with_options
             if is_user_defined_option(opt)
         ]
 
index c1da9b06a4d344655468dd5438085df5d406572b..b2407b3f2f0a95d195cf70d85d00c650f21da0c3 100644 (file)
@@ -239,6 +239,35 @@ class ORMExecuteTest(RemoveORMEventsGlobally, _fixtures.FixtureTest):
             ),
         )
 
+    @testing.combinations("select", "query", "not-select")
+    def test_user_option_propagation_after_with_only_columns(self, operation):
+        User = self.classes("User")[0]
+
+        class MyOption(UserDefinedOption):
+            pass
+
+        s = fixture_session()
+        found = False
+
+        @event.listens_for(s, "do_orm_execute")
+        def go(context):
+            nonlocal found
+            for elem in context.user_defined_options:
+                if isinstance(elem, MyOption):
+                    found = True
+
+        if operation == "select":
+            stmt = select(User).options(MyOption()).with_only_columns(User.id)
+            s.execute(stmt).all()
+        elif operation == "query":
+            stmt = s.query(User).options(MyOption()).with_entities(User.id)
+            stmt.all()
+        elif operation == "not-select":
+            stmt = insert(User).values(name="new name").options(MyOption())
+            s.execute(stmt)
+
+        eq_(found, True)
+
     def test_override_parameters_scalar(self):
         """test that session.scalar() maintains the 'scalar-ness' of the
         result when using re-execute events.