summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/build/changelog/unreleased_20/9519.rst10
-rw-r--r--lib/sqlalchemy/orm/attributes.py4
-rw-r--r--test/ext/test_hybrid.py57
3 files changed, 69 insertions, 2 deletions
diff --git a/doc/build/changelog/unreleased_20/9519.rst b/doc/build/changelog/unreleased_20/9519.rst
new file mode 100644
index 000000000..c5a24a442
--- /dev/null
+++ b/doc/build/changelog/unreleased_20/9519.rst
@@ -0,0 +1,10 @@
+.. change::
+ :tags: bug, orm
+ :tickets: 9519
+
+ Fixed regression where accessing the expression value of a hybrid property
+ on a class that was either unmapped or not-yet-mapped (such as calling upon
+ it within a :func:`_orm.declared_attr` method) would raise an internal
+ error, as an internal fetch for the parent class' mapper would fail and an
+ instruction for this failure to be ignored were inadvertently removed in
+ 2.0.
diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py
index f33364c62..3a60eda4f 100644
--- a/lib/sqlalchemy/orm/attributes.py
+++ b/lib/sqlalchemy/orm/attributes.py
@@ -611,11 +611,11 @@ def create_proxied_attribute(
@property
def _parententity(self):
- return inspection.inspect(self.class_)
+ return inspection.inspect(self.class_, raiseerr=False)
@property
def parent(self):
- return inspection.inspect(self.class_)
+ return inspection.inspect(self.class_, raiseerr=False)
_is_internal_proxy = True
diff --git a/test/ext/test_hybrid.py b/test/ext/test_hybrid.py
index 73967cdd0..69e9c1335 100644
--- a/test/ext/test_hybrid.py
+++ b/test/ext/test_hybrid.py
@@ -15,7 +15,9 @@ from sqlalchemy import String
from sqlalchemy import testing
from sqlalchemy.ext import hybrid
from sqlalchemy.orm import aliased
+from sqlalchemy.orm import column_property
from sqlalchemy.orm import declarative_base
+from sqlalchemy.orm import declared_attr
from sqlalchemy.orm import relationship
from sqlalchemy.orm import Session
from sqlalchemy.orm import synonym
@@ -421,6 +423,61 @@ class PropertyExpressionTest(fixtures.TestBase, AssertsCompiledSQL):
return A
+ def test_access_from_unmapped(self):
+ """test #9519"""
+
+ class DnsRecord:
+ name = Column("name", String)
+
+ @hybrid.hybrid_property
+ def ip_value(self):
+ return self.name[1:3]
+
+ @ip_value.expression
+ def ip_value(cls):
+ return func.substring(cls.name, 1, 3)
+
+ raw_attr = DnsRecord.ip_value
+ is_(raw_attr._parententity, None)
+
+ self.assert_compile(
+ raw_attr, "substring(name, :substring_1, :substring_2)"
+ )
+
+ self.assert_compile(
+ select(DnsRecord.ip_value),
+ "SELECT substring(name, :substring_2, :substring_3) "
+ "AS substring_1",
+ )
+
+ def test_access_from_not_yet_mapped(self, decl_base):
+ """test #9519"""
+
+ class DnsRecord(decl_base):
+ __tablename__ = "dnsrecord"
+ id = Column(Integer, primary_key=True)
+ name = Column(String, unique=False, nullable=False)
+
+ @declared_attr
+ def thing(cls):
+ return column_property(cls.ip_value)
+
+ name = Column("name", String)
+
+ @hybrid.hybrid_property
+ def ip_value(self):
+ return self.name[1:3]
+
+ @ip_value.expression
+ def ip_value(cls):
+ return func.substring(cls.name, 1, 3)
+
+ self.assert_compile(
+ select(DnsRecord.thing),
+ "SELECT substring(dnsrecord.name, :substring_2, :substring_3) "
+ "AS substring_1 FROM dnsrecord",
+ )
+
def test_labeling_for_unnamed(self, _unnamed_expr_fixture):
A = _unnamed_expr_fixture