--- /dev/null
+.. change::
+ :tags: bug, orm
+ :tickets: 4767
+
+ Fixed bug where a synonym created against a mapped attribute that does not
+ exist yet, as is the case when it refers to backref before mappers are
+ configured, would raise recursion errors when trying to test for attributes
+ on it which ultimately don't exist (as occurs when the classes are run
+ through Sphinx autodoc), as the unconfigured state of the synonym would put
+ it into an attribute not found loop.
+
def __getattr__(self, attribute):
"""Delegate __getattr__ to the original descriptor and/or
comparator."""
-
try:
return getattr(descriptor, attribute)
except AttributeError:
+ if attribute == "comparator":
+ raise AttributeError("comparator")
try:
- return getattr(self.comparator, attribute)
+ # comparator itself might be unreachable
+ comparator = self.comparator
except AttributeError:
raise AttributeError(
- "Neither %r object nor %r object associated with %s "
- "has an attribute %r"
- % (
- type(descriptor).__name__,
- type(self.comparator).__name__,
- self,
- attribute,
- )
+ "Neither %r object nor unconfigured comparator "
+ "object associated with %s has an attribute %r"
+ % (type(descriptor).__name__, self, attribute)
)
+ else:
+ try:
+ return getattr(comparator, attribute)
+ except AttributeError:
+ raise AttributeError(
+ "Neither %r object nor %r object "
+ "associated with %s has an attribute %r"
+ % (
+ type(descriptor).__name__,
+ type(comparator).__name__,
+ self,
+ attribute,
+ )
+ )
Proxy.__name__ = type(descriptor).__name__ + "Proxy"
eq_(attributes.instance_state(u1).attrs.x.history, ([5], (), ()))
eq_(attributes.instance_state(u1).attrs.y.history, ([5], (), ()))
+ def test_synonym_nonexistent_attr(self):
+ # test [ticket:4767].
+ # synonym points to non-existent attrbute that hasn't been mapped yet.
+ users = self.tables.users
+
+ class User(object):
+ def _x(self):
+ return self.id
+
+ x = property(_x)
+
+ m = mapper(
+ User,
+ users,
+ properties={"x": synonym("some_attr", descriptor=User.x)},
+ )
+
+ # object gracefully handles this condition
+ assert not hasattr(User.x, "__name__")
+ assert not hasattr(User.x, "comparator")
+
+ m.add_property("some_attr", column_property(users.c.name))
+
+ assert not hasattr(User.x, "__name__")
+ assert hasattr(User.x, "comparator")
+
def test_synonym_of_non_property_raises(self):
from sqlalchemy.ext.associationproxy import association_proxy