Intercept unresolveable comparator attrbute error for attr access

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.

Fixes: #4767
Change-Id: I9aade8628349fbf538181a0049416cec0a17179c
(cherry picked from commit 234723fa29)
This commit is contained in:
Mike Bayer
2019-07-16 13:02:16 -04:00
parent 7e161e8347
commit cfac04da2a
3 changed files with 58 additions and 10 deletions
+11
View File
@@ -0,0 +1,11 @@
.. 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.
+21 -10
View File
@@ -365,23 +365,34 @@ def create_proxied_attribute(descriptor):
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"
+26
View File
@@ -1535,6 +1535,32 @@ class MapperTest(_fixtures.FixtureTest, AssertsCompiledSQL):
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