]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
catch unexpected errors when accessing clslevel attribute
authorMike Bayer <mike_mp@zzzcomputing.com>
Fri, 18 Mar 2022 14:33:40 +0000 (10:33 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Fri, 18 Mar 2022 14:34:25 +0000 (10:34 -0400)
Improved the error message that's raised for the case where the
:func:`.association_proxy` construct attempts to access a target attribute
at the class level, and this access fails. The particular use case here is
when proxying to a hybrid attribute that does not include a working
class-level implementation.

Fixes: #7827
Change-Id: Ic6ff9df010f49253e664a1e7c7e16d8546006965

doc/build/changelog/unreleased_14/7827.rst [new file with mode: 0644]
lib/sqlalchemy/ext/associationproxy.py
test/ext/test_associationproxy.py

diff --git a/doc/build/changelog/unreleased_14/7827.rst b/doc/build/changelog/unreleased_14/7827.rst
new file mode 100644 (file)
index 0000000..aedf258
--- /dev/null
@@ -0,0 +1,10 @@
+.. change::
+    :tags: bug, ext
+    :tickets: 7827
+
+    Improved the error message that's raised for the case where the
+    :func:`.association_proxy` construct attempts to access a target attribute
+    at the class level, and this access fails. The particular use case here is
+    when proxying to a hybrid attribute that does not include a working
+    class-level implementation.
+
index 4d2b1d8b69c59f73c9b02faf381e33acd2a0511a..194eabb64ebf90933caba2018a577ce60077b264 100644 (file)
@@ -583,6 +583,13 @@ class AssociationProxyInstance(SQLORMOperations[_T]):
             return AmbiguousAssociationProxyInstance(
                 parent, owning_class, target_class, value_attr
             )
+        except Exception as err:
+            raise exc.InvalidRequestError(
+                f"Association proxy received an unexpected error when "
+                f"trying to retreive attribute "
+                f'"{target_class.__name__}.{parent.value_attr}" from '
+                f'class "{target_class.__name__}": {err}'
+            ) from err
         else:
             return cls._construct_for_assoc(
                 target_assoc, parent, owning_class, target_class, value_attr
index 484aed7953d7c4f4c10fccef22d06814cf181da5..a99b23338ff3dda97d6f806933ca4f365f129124 100644 (file)
@@ -37,6 +37,7 @@ from sqlalchemy.testing import expect_warnings
 from sqlalchemy.testing import fixtures
 from sqlalchemy.testing import is_
 from sqlalchemy.testing import is_false
+from sqlalchemy.testing.assertions import expect_raises_message
 from sqlalchemy.testing.fixtures import fixture_session
 from sqlalchemy.testing.schema import Column
 from sqlalchemy.testing.schema import Table
@@ -3365,6 +3366,10 @@ class ProxyHybridTest(fixtures.DeclarativeMappedTest, AssertsCompiledSQL):
             b_data = association_proxy("bs", "value")
             well_behaved_b_data = association_proxy("bs", "well_behaved_value")
 
+            fails_on_class_access = association_proxy(
+                "bs", "fails_on_class_access"
+            )
+
         class B(Base):
             __tablename__ = "b"
 
@@ -3408,6 +3413,10 @@ class ProxyHybridTest(fixtures.DeclarativeMappedTest, AssertsCompiledSQL):
             def well_behaved_w_expr(cls):
                 return cast(cls.data, Integer)
 
+            @hybrid_property
+            def fails_on_class_access(self):
+                return len(self.data)
+
         class C(Base):
             __tablename__ = "c"
 
@@ -3416,6 +3425,19 @@ class ProxyHybridTest(fixtures.DeclarativeMappedTest, AssertsCompiledSQL):
             _b = relationship("B")
             attr = association_proxy("_b", "well_behaved_w_expr")
 
+    def test_msg_fails_on_cls_access(self):
+        A, B = self.classes("A", "B")
+
+        a1 = A(bs=[B(data="b1")])
+
+        with expect_raises_message(
+            exc.InvalidRequestError,
+            "Association proxy received an unexpected error when trying to "
+            'retreive attribute "B.fails_on_class_access" from '
+            r'class "B": .* no len\(\)',
+        ):
+            a1.fails_on_class_access
+
     def test_get_ambiguous(self):
         A, B = self.classes("A", "B")