From: Mike Bayer Date: Mon, 12 Aug 2024 23:50:05 +0000 (-0400) Subject: de-memoize _proxy_key when new annotations are added X-Git-Tag: rel_1_4_54~6 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3b5f0fcfbe5fe6bca5566d4a37b28cfe2bd89f8d;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git de-memoize _proxy_key when new annotations are added Fixed regression from 1.3 where the column key used for a hybrid property might be populated with that of the underlying column that it returns, for a property that returns an ORM mapped column directly, rather than the key used by the hybrid property itself. Fixes: #11728 Change-Id: Ifb298e46a20f90f6b6a717674f142a87cbceb468 (cherry picked from commit ffc7e8d73b30ea45fb03e0727b9fe96b6b8d4cfa) (cherry picked from commit 5b1758a9bb8952adca91a95483ec1d11a66ad1e2) --- diff --git a/doc/build/changelog/unreleased_14/11728.rst b/doc/build/changelog/unreleased_14/11728.rst new file mode 100644 index 0000000000..b27aa3333d --- /dev/null +++ b/doc/build/changelog/unreleased_14/11728.rst @@ -0,0 +1,9 @@ +.. change:: + :tags: bug, regression, orm + :tickets: 11728 + :versions: 2.0.33 + + Fixed regression from 1.3 where the column key used for a hybrid property + might be populated with that of the underlying column that it returns, for + a property that returns an ORM mapped column directly, rather than the key + used by the hybrid property itself. diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py index 7671e75d48..96f2936fe7 100644 --- a/lib/sqlalchemy/sql/elements.py +++ b/lib/sqlalchemy/sql/elements.py @@ -5283,7 +5283,14 @@ class AnnotatedColumnElement(Annotated): def _with_annotations(self, values): clone = super(AnnotatedColumnElement, self)._with_annotations(values) - clone.__dict__.pop("comparator", None) + for attr in ( + "comparator", + "_proxy_key", + "_tq_key_label", + "_tq_label", + "_non_anon_label", + ): + clone.__dict__.pop(attr, None) return clone @util.memoized_property diff --git a/test/ext/test_hybrid.py b/test/ext/test_hybrid.py index be42fdb6d0..caee584a0e 100644 --- a/test/ext/test_hybrid.py +++ b/test/ext/test_hybrid.py @@ -5,6 +5,7 @@ from sqlalchemy import ForeignKey from sqlalchemy import func from sqlalchemy import inspect from sqlalchemy import Integer +from sqlalchemy import LABEL_STYLE_DISAMBIGUATE_ONLY from sqlalchemy import LABEL_STYLE_TABLENAME_PLUS_COL from sqlalchemy import literal_column from sqlalchemy import Numeric @@ -322,6 +323,21 @@ class PropertyExpressionTest(fixtures.TestBase, AssertsCompiledSQL): return A + @testing.fixture + def _unnamed_expr_matches_col_fixture(self): + Base = declarative_base() + + class A(Base): + __tablename__ = "a" + id = Column(Integer, primary_key=True) + foo = Column(String) + + @hybrid.hybrid_property + def bar(self): + return self.foo + + return A + def test_labeling_for_unnamed(self, _unnamed_expr_fixture): A = _unnamed_expr_fixture @@ -341,6 +357,39 @@ class PropertyExpressionTest(fixtures.TestBase, AssertsCompiledSQL): "a.lastname AS name FROM a) AS anon_1", ) + @testing.variation("pre_populate_col_proxy", [True, False]) + def test_labeling_for_unnamed_matches_col( + self, _unnamed_expr_matches_col_fixture, pre_populate_col_proxy + ): + """test #11728""" + + A = _unnamed_expr_matches_col_fixture + + if pre_populate_col_proxy: + pre_stmt = select(A.id, A.foo) + pre_stmt.subquery().c + + stmt = select(A.id, A.bar) + self.assert_compile( + stmt, + "SELECT a.id, a.foo FROM a", + ) + + compile_state = stmt._compile_state_factory(stmt, None) + eq_( + compile_state._column_naming_convention( + LABEL_STYLE_DISAMBIGUATE_ONLY, legacy=False + )(list(stmt.inner_columns)[1]), + "bar", + ) + eq_(stmt.subquery().c.keys(), ["id", "bar"]) + + self.assert_compile( + select(stmt.subquery()), + "SELECT anon_1.id, anon_1.foo FROM " + "(SELECT a.id AS id, a.foo AS foo FROM a) AS anon_1", + ) + def test_labeling_for_unnamed_tablename_plus_col( self, _unnamed_expr_fixture ):