From: Federico Caselli Date: Tue, 19 May 2026 21:39:49 +0000 (+0200) Subject: user_defined_options returns memoized options X-Git-Url: http://git.ipfire.org/gitweb/index.cgi?a=commitdiff_plain;h=68e982ad9de3c2300ea1927c099e3489b5baa6bc;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git user_defined_options returns memoized options 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 --- diff --git a/doc/build/changelog/unreleased_21/13309.rst b/doc/build/changelog/unreleased_21/13309.rst new file mode 100644 index 0000000000..a97686113a --- /dev/null +++ b/doc/build/changelog/unreleased_21/13309.rst @@ -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`. diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py index cb37588ede..d03c90047a 100644 --- a/lib/sqlalchemy/orm/session.py +++ b/lib/sqlalchemy/orm/session.py @@ -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) ] diff --git a/test/orm/test_events.py b/test/orm/test_events.py index c1da9b06a4..b2407b3f2f 100644 --- a/test/orm/test_events.py +++ b/test/orm/test_events.py @@ -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.