summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/build/changelog/unreleased_14/6386.rst9
-rw-r--r--lib/sqlalchemy/orm/attributes.py1
-rw-r--r--lib/sqlalchemy/orm/context.py3
-rw-r--r--test/ext/test_hybrid.py63
-rw-r--r--test/orm/test_utils.py2
5 files changed, 77 insertions, 1 deletions
diff --git a/doc/build/changelog/unreleased_14/6386.rst b/doc/build/changelog/unreleased_14/6386.rst
new file mode 100644
index 000000000..d61a2cc48
--- /dev/null
+++ b/doc/build/changelog/unreleased_14/6386.rst
@@ -0,0 +1,9 @@
+.. change::
+ :tags: ext, bug, regression
+ :tickets: 6386
+
+ Fixed regression in ORM where using hybrid property to indicate an
+ expression from a different entity would confuse the column-labeling logic
+ in the ORM and attempt to derive the name of the hybrid from that other
+ class, leading to an attribute error. The owning class of the hybrid
+ attribute is now tracked along with the name. \ No newline at end of file
diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py
index 0c7bc4cf0..b8974196c 100644
--- a/lib/sqlalchemy/orm/attributes.py
+++ b/lib/sqlalchemy/orm/attributes.py
@@ -227,6 +227,7 @@ class QueryableAttribute(
else:
annotations = {
"proxy_key": self.key,
+ "proxy_owner": self.class_,
"entity_namespace": self._entity_namespace,
}
diff --git a/lib/sqlalchemy/orm/context.py b/lib/sqlalchemy/orm/context.py
index f62115fda..55b61b19e 100644
--- a/lib/sqlalchemy/orm/context.py
+++ b/lib/sqlalchemy/orm/context.py
@@ -2691,8 +2691,9 @@ class _ORMColumnEntity(_ColumnEntity):
# within internal loaders.
orm_key = annotations.get("proxy_key", None)
+ proxy_owner = annotations.get("proxy_owner", _entity.entity)
if orm_key:
- self.expr = getattr(_entity.entity, orm_key)
+ self.expr = getattr(proxy_owner, orm_key)
self.translate_raw_column = False
else:
# if orm_key is not present, that means this is an ad-hoc
diff --git a/test/ext/test_hybrid.py b/test/ext/test_hybrid.py
index c9373f9aa..ee991782e 100644
--- a/test/ext/test_hybrid.py
+++ b/test/ext/test_hybrid.py
@@ -492,6 +492,69 @@ class PropertyMirrorTest(fixtures.TestBase, AssertsCompiledSQL):
return A
+ @testing.fixture
+ def _name_mismatch_fixture(self):
+ Base = declarative_base()
+
+ class A(Base):
+ __tablename__ = "a"
+ id = Column(Integer, primary_key=True)
+ addresses = relationship("B")
+
+ @hybrid.hybrid_property
+ def some_email(self):
+ if self.addresses:
+ return self.addresses[0].email_address
+ else:
+ return None
+
+ @some_email.expression
+ def some_email(cls):
+ return B.email_address
+
+ class B(Base):
+ __tablename__ = "b"
+ id = Column(Integer, primary_key=True)
+ aid = Column(ForeignKey("a.id"))
+ email_address = Column(String)
+
+ return A, B
+
+ def test_dont_assume_attr_key_is_present(self, _name_mismatch_fixture):
+ A, B = _name_mismatch_fixture
+ self.assert_compile(
+ select(A, A.some_email).join(A.addresses),
+ "SELECT a.id, b.email_address FROM a JOIN b ON a.id = b.aid",
+ )
+
+ def test_dont_assume_attr_key_is_present_ac(self, _name_mismatch_fixture):
+ A, B = _name_mismatch_fixture
+
+ ac = aliased(A)
+ self.assert_compile(
+ select(ac, ac.some_email).join(ac.addresses),
+ "SELECT a_1.id, b.email_address "
+ "FROM a AS a_1 JOIN b ON a_1.id = b.aid",
+ )
+
+ def test_filter_by_mismatched_col(self, _name_mismatch_fixture):
+ A, B = _name_mismatch_fixture
+ self.assert_compile(
+ select(A).filter_by(some_email="foo").join(A.addresses),
+ "SELECT a.id FROM a JOIN b ON a.id = b.aid "
+ "WHERE b.email_address = :email_address_1",
+ )
+
+ def test_aliased_mismatched_col(self, _name_mismatch_fixture):
+ A, B = _name_mismatch_fixture
+ sess = fixture_session()
+
+ # so what should this do ? it's just a weird hybrid case
+ self.assert_compile(
+ sess.query(aliased(A).some_email),
+ "SELECT b.email_address AS b_email_address FROM b",
+ )
+
def test_property(self):
A = self._fixture()
diff --git a/test/orm/test_utils.py b/test/orm/test_utils.py
index 8b298c6fb..c0829e9b3 100644
--- a/test/orm/test_utils.py
+++ b/test/orm/test_utils.py
@@ -246,6 +246,7 @@ class AliasedClassTest(fixtures.TestBase, AssertsCompiledSQL):
"parententity": point_mapper,
"parentmapper": point_mapper,
"proxy_key": "x_alone",
+ "proxy_owner": Point,
},
)
eq_(
@@ -255,6 +256,7 @@ class AliasedClassTest(fixtures.TestBase, AssertsCompiledSQL):
"parententity": point_mapper,
"parentmapper": point_mapper,
"proxy_key": "x",
+ "proxy_owner": Point,
},
)