diff options
| -rw-r--r-- | doc/build/changelog/unreleased_20/9519.rst | 10 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/attributes.py | 4 | ||||
| -rw-r--r-- | test/ext/test_hybrid.py | 57 |
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 |
